Skip to content

Autogen instructions & opcodes#7797

Merged
youknowone merged 35 commits into
RustPython:mainfrom
ShaharNaveh:autogen-opcodes
May 10, 2026
Merged

Autogen instructions & opcodes#7797
youknowone merged 35 commits into
RustPython:mainfrom
ShaharNaveh:autogen-opcodes

Conversation

@ShaharNaveh
Copy link
Copy Markdown
Contributor

@ShaharNaveh ShaharNaveh commented May 7, 2026

I intend to break this PR to multiple PRs.

Posting this as a draft for now so I can get early feedback on my general idea.
Even if this doesn't get merged, we can make use of some auto-generated parts here (maybe)

Summary by CodeRabbit

  • Chores
    • Refactored internal compiler infrastructure for bytecode instruction handling and opcode metadata management
    • Improved code generation system for better maintainability and consistency

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

📝 Walkthrough

Walkthrough

This PR refactors the RustPython bytecode instruction system to generate Rust opcode/instruction enums from a TOML configuration file, removes the InstructionMetadata trait in favor of inherent methods, reorganizes module exports, and updates all call sites to use the new APIs and async iteration instruction variant names.

Changes

Bytecode Opcode System Refactoring

Layer / File(s) Summary
Opcode Configuration & Code Generator
crates/compiler-core/opcode.toml, crates/compiler-core/generate.py
TOML file defines opcode metadata with instruction enum names, numeric representations, operand types, and stack effects. Python generator script reads TOML and opcode analysis, emits Rust instruction/opcode enums plus conversion/classification/stack-effect helper methods via OpcodeGen and InstructionGen classes; output is formatted with rustfmt.
Module Restructuring & Re-exports
crates/compiler-core/src/bytecode.rs, crates/compiler-core/src/bytecode/oparg.rs
Renames instruction module to instructions; updates bytecode.rs to re-export Instruction/Opcode/pseudo types from instructions instead of instruction; removes InstructionMetadata from public re-exports; updates oparg.rs import path.
Trait Refactoring: InstructionMetadata → Inherent Methods
crates/compiler-core/src/bytecode/instruction.rs
Removes public InstructionMetadata trait; adds inherent methods to Opcode (deoptimize, is_unconditional_jump, is_scope_exit, is_block_push, stack_effect_jump); adds inherent methods to PseudoOpcode (is_instrumented, is_block_push, stack_effect_jump); adds delegation methods on Instruction/PseudoInstruction to forward calls to their opcode/pseudo-opcode types; adds macro-generated query and stack-effect delegation methods on AnyInstruction and AnyOpcode.
Stdlib Opcode Module Updates
crates/stdlib/src/_opcode.rs
Imports AnyOpcode directly instead of AnyInstruction; refactors stack_effect to parse via AnyOpcode::try_from; replaces hardcoded has_arg/has_const/has_name/has_jump/has_free/has_local/has_exc match tables with calls to corresponding AnyOpcode methods; removes HAVE_ARGUMENT constant.
Codegen IR Updates
crates/codegen/src/ir.rs
Removes InstructionMetadata from imports; updates three instruction match sites to use Instruction::GetAnext variant (in optimize_load_fast_borrow, block_has_get_anext, block_contains_suspension_point).
VM Frame Updates
crates/vm/src/frame.rs, crates/vm/src/builtins/frame.rs
Remove InstructionMetadata imports; update bytecode dispatch and stack analysis to use Instruction::GetAiter/Instruction::GetAnext variant names (renamed from GetAIter/GetANext).
Async Iteration Instruction Renaming
crates/codegen/src/compile.rs
Update async-for, async comprehension, async generator emission and test assertions to use Instruction::GetAiter/Instruction::GetAnext variant names (new casing).
Metadata Generation Script
scripts/generate_opcode_metadata.py
Update BYTECODE_FILE to read from instructions.rs; refactor Opcode class to compute cpython_name property from rust_name; adjust ID parsing regex to match = <number> format; update deoptimization regex to match Self:: variant prefix instead of `Opcode::``.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • RustPython/RustPython#7615: Both PRs refactor bytecode instruction deoptimization logic, with this PR introducing deopt methods on Opcode and the related PR further simplifying Instruction::deopt delegation.
  • RustPython/RustPython#7573: Both PRs modify bytecode instruction/opcode types, module organization (instruction→instructions), and code generation infrastructure in parallel.
  • RustPython/RustPython#7757: Both PRs modify instruction matching in crates/codegen/src/ir.rs, including async iteration instruction variant handling and opcode-level method delegation.

Suggested reviewers

  • youknowone

Poem

🐰 Whisker-twitch with delight!
Opcodes refactored, traits take flight,
From metadata trait to methods so keen,
TOML config and generated Rust sheen! ✨
Async iterations renamed just right,
The bytecode hops forward with newfound might! 🦘

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Autogen instructions & opcodes' accurately and specifically describes the main change in the pull request, which introduces an autogeneration system for instructions and opcodes across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ShaharNaveh ShaharNaveh requested a review from youknowone May 7, 2026 14:45
@ShaharNaveh ShaharNaveh requested a review from fanninpm May 7, 2026 16:17
@ShaharNaveh ShaharNaveh marked this pull request as ready for review May 9, 2026 08:34
@ShaharNaveh ShaharNaveh changed the title [WIP] Autogen instructions & opcodes Autogen instructions & opcodes May 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
crates/compiler-core/generate.py (2)

18-21: 💤 Low value

Chain the re-raised exception (B904).

Inside the except KeyError clause, raise the new error with explicit chaining so traceback context is preserved (and to silence the Ruff B904 warning).

♻️ Proposed fix
 try:
     CPYTHON_ROOT = pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve()
-except KeyError:
-    raise ValueError("Missing environment variable 'CPYTHON_ROOT'")
+except KeyError as e:
+    raise ValueError("Missing environment variable 'CPYTHON_ROOT'") from e

As per coding guidelines: "Use ruff for linting Python code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/generate.py` around lines 18 - 21, The except KeyError
block re-raises a new ValueError without exception chaining, triggering Ruff
B904; update the except to capture the original KeyError (e.g., except KeyError
as e) and re-raise the ValueError with explicit chaining using "from e" so the
traceback context for CPYTHON_ROOT resolution
(pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve()) is preserved.

462-470: 💤 Low value

Walrus binds oparg but the body never reads it (F841).

In fn_as_opcode, the walrus operator assigns oparg only to gate the arms += " { .. }" branch. Since the value isn't used, a plain containment check is clearer and avoids the dead binding flagged by Flake8.

♻️ Proposed fix
-        for instr in self:
-            name = instr.name
-            arms += f"Self::{name}"
-            if oparg := self.metadata.get(name, {}).get("oparg"):
-                arms += " { .. }"
-
-            arms += f"=> {self.opcode_enum}::{name},\n"
+        for instr in self:
+            name = instr.name
+            arms += f"Self::{name}"
+            if "oparg" in self.metadata.get(name, {}):
+                arms += " { .. }"
+
+            arms += f" => {self.opcode_enum}::{name},\n"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/generate.py` around lines 462 - 470, The walrus in
fn_as_opcode assigns oparg but never uses it, triggering Flake8 F841; replace
the walrus check in the loop (where arms is built for each instr/name) with a
plain containment check against self.metadata for "oparg" (e.g., use if "oparg"
in self.metadata.get(name, {}) or if self.metadata.get(name, {}).get("oparg")
without assignment) so the branch that appends " { .. }" runs without creating
an unused binding.
crates/compiler-core/src/bytecode/instruction.rs (1)

480-498: 💤 Low value

AnyOpcode::deopt can be flattened slightly without losing const-ness.

The if let Some(_) { Some(_) } else { None } shape is forced because Option::map isn't usable in a const fn. That's fine, but a match is a touch shorter and matches the style used by neighbouring real/pseudo accessors above:

♻️ Proposed sketch
     #[must_use]
     pub const fn deopt(&self) -> Option<Self> {
         match self {
-            Self::Real(opcode) => {
-                if let Some(op) = opcode.deopt() {
-                    Some(Self::Real(op))
-                } else {
-                    None
-                }
-            }
-            Self::Pseudo(opcode) => {
-                if let Some(op) = opcode.deopt() {
-                    Some(Self::Pseudo(op))
-                } else {
-                    None
-                }
-            }
+            Self::Real(opcode) => match opcode.deopt() {
+                Some(op) => Some(Self::Real(op)),
+                None => None,
+            },
+            Self::Pseudo(opcode) => match opcode.deopt() {
+                Some(op) => Some(Self::Pseudo(op)),
+                None => None,
+            },
         }
     }

Same match-on-Option pattern is already used by Opcode::deoptimize at the top of this file, so this is also a consistency nit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 480 - 498, The
AnyOpcode::deopt const fn can be simplified: inside each arm (Self::Real(opcode)
and Self::Pseudo(opcode)) replace the if-let Some/else None pattern with a match
on opcode.deopt() that returns Some(Self::Real(op)) / Some(Self::Pseudo(op)) or
None, preserving const fn and behavior and matching the style used by
Opcode::deoptimize and the nearby accessors.
crates/stdlib/src/_opcode.rs (1)

70-145: ⚡ Quick win

Please add direct _opcode regression coverage.

This changes Python-visible behavior for stack_effect() and the has_* helpers, but the tests below still only snapshot dis.dis. A few focused assertions for valid, invalid, and specialized opcodes would make future generator changes much safer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/_opcode.rs` around lines 70 - 145, Add direct unit tests
targeting the _opcode module: call the Python-exposed functions stack_effect
(with jump true/false/None), is_valid, has_arg, has_const, has_name, has_jump,
has_free, has_local, and has_exc to assert expected behavior for (1) a known
valid opcode (e.g., LOAD_CONST or POP_TOP) returns correct stack effects and
true for the corresponding has_* helpers, (2) an invalid opcode integer causes
is_valid False and stack_effect raises/returns the ValueError behavior observed
in the implementation, and (3) a specialized opcode (one whose deopt() is Some)
triggers the ValueError path in stack_effect; keep tests focused and small so
future opcode generator changes are caught without relying only on dis.dis
snapshots.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/compiler-core/opcode.toml`:
- Around line 269-270: The oparg type for
PseudoOpcode.opcodes.StoreFastMaybeNull is incorrect: change the var_num oparg
from oparg::NameIdx to oparg::VarNum so PseudoInstruction::StoreFastMaybeNull is
generated with var_num: Arg<oparg::VarNum> (matching other var_num uses like
StoreFast/LoadFast) to index localsplus and preserve the VarNum newtype's type
safety.

In `@crates/compiler-core/src/bytecode/instruction.rs`:
- Around line 49-58: The doc-comment promises that stack_effect_jump differs for
certain ops like Self::ForIter, but stack_effect_jump currently just delegates
to stack_effect(oparg), so ForIter's branch effect isn't handled; update
stack_effect_jump to match on instruction variants (e.g., match self {
Self::ForIter => -1, /* other special cases */ ... , _ =>
self.stack_effect(oparg) }) so branch (jump=true) effects are correct, or
alternatively remove the misleading ForIter example from the doc-comment if you
do not want to implement overrides now; ensure callers that rely on
stack_effect_jump (the call-site noted in the review) will receive correct
jump-edge counts.
- Around line 121-124: Instruction::stack_effect_jump incorrectly delegates to
Opcode::stack_effect instead of Opcode::stack_effect_jump; update the method to
call self.as_opcode().stack_effect_jump(oparg) so it mirrors
PseudoInstruction::stack_effect_jump and correctly uses the branch-specific
stack effect behavior for opcodes like ForIter (use as_opcode(),
stack_effect_jump, and Instruction::stack_effect_jump to locate the change).

---

Nitpick comments:
In `@crates/compiler-core/generate.py`:
- Around line 18-21: The except KeyError block re-raises a new ValueError
without exception chaining, triggering Ruff B904; update the except to capture
the original KeyError (e.g., except KeyError as e) and re-raise the ValueError
with explicit chaining using "from e" so the traceback context for CPYTHON_ROOT
resolution (pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve()) is
preserved.
- Around line 462-470: The walrus in fn_as_opcode assigns oparg but never uses
it, triggering Flake8 F841; replace the walrus check in the loop (where arms is
built for each instr/name) with a plain containment check against self.metadata
for "oparg" (e.g., use if "oparg" in self.metadata.get(name, {}) or if
self.metadata.get(name, {}).get("oparg") without assignment) so the branch that
appends " { .. }" runs without creating an unused binding.

In `@crates/compiler-core/src/bytecode/instruction.rs`:
- Around line 480-498: The AnyOpcode::deopt const fn can be simplified: inside
each arm (Self::Real(opcode) and Self::Pseudo(opcode)) replace the if-let
Some/else None pattern with a match on opcode.deopt() that returns
Some(Self::Real(op)) / Some(Self::Pseudo(op)) or None, preserving const fn and
behavior and matching the style used by Opcode::deoptimize and the nearby
accessors.

In `@crates/stdlib/src/_opcode.rs`:
- Around line 70-145: Add direct unit tests targeting the _opcode module: call
the Python-exposed functions stack_effect (with jump true/false/None), is_valid,
has_arg, has_const, has_name, has_jump, has_free, has_local, and has_exc to
assert expected behavior for (1) a known valid opcode (e.g., LOAD_CONST or
POP_TOP) returns correct stack effects and true for the corresponding has_*
helpers, (2) an invalid opcode integer causes is_valid False and stack_effect
raises/returns the ValueError behavior observed in the implementation, and (3) a
specialized opcode (one whose deopt() is Some) triggers the ValueError path in
stack_effect; keep tests focused and small so future opcode generator changes
are caught without relying only on dis.dis snapshots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 5f2bd321-730f-4d37-9cf6-fcd6aeaa987a

📥 Commits

Reviewing files that changed from the base of the PR and between e10a27b and d90b993.

📒 Files selected for processing (12)
  • crates/codegen/src/compile.rs
  • crates/codegen/src/ir.rs
  • crates/compiler-core/generate.py
  • crates/compiler-core/opcode.toml
  • crates/compiler-core/src/bytecode.rs
  • crates/compiler-core/src/bytecode/instruction.rs
  • crates/compiler-core/src/bytecode/instructions.rs
  • crates/compiler-core/src/bytecode/oparg.rs
  • crates/stdlib/src/_opcode.rs
  • crates/vm/src/builtins/frame.rs
  • crates/vm/src/frame.rs
  • scripts/generate_opcode_metadata.py

Comment on lines +269 to +270
[PseudoOpcode.opcodes.StoreFastMaybeNull]
oparg = { name = "var_num", type = "oparg::NameIdx" }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrong oparg type for StoreFastMaybeNull.

The field is named var_num (consistent with StoreFast, LoadFast, etc.), but the type is set to oparg::NameIdx instead of oparg::VarNum. Every other var_num entry in this file (e.g., lines 64, 121, 124, 127, 133, 205) uses oparg::VarNum. As written, the generated PseudoInstruction::StoreFastMaybeNull { var_num: Arg<oparg::NameIdx> } would index into names, not localsplus, and lose the VarNum newtype's type safety (NameIdx is just pub type NameIdx = u32;).

🐛 Proposed fix
 [PseudoOpcode.opcodes.StoreFastMaybeNull]
-oparg = { name = "var_num", type = "oparg::NameIdx" }
+oparg = { name = "var_num", type = "oparg::VarNum" }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/opcode.toml` around lines 269 - 270, The oparg type for
PseudoOpcode.opcodes.StoreFastMaybeNull is incorrect: change the var_num oparg
from oparg::NameIdx to oparg::VarNum so PseudoInstruction::StoreFastMaybeNull is
generated with var_num: Arg<oparg::VarNum> (matching other var_num uses like
StoreFast/LoadFast) to index localsplus and preserve the VarNum newtype's type
safety.

Comment on lines +49 to 58
/// Stack effect when the instruction takes its branch (jump=true).
///
/// CPython equivalent: `stack_effect(opcode, oparg, jump=True)`.
/// For most instructions this equals the fallthrough effect.
/// Override for instructions where branch and fallthrough differ
/// (e.g. [`Self::ForIter`]: fallthrough = +1, branch = −1).
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.stack_effect(oparg)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Doc-comment promises a ForIter override that isn't implemented.

The comment states "Override for instructions where branch and fallthrough differ (e.g. Self::ForIter: fallthrough = +1, branch = −1)", but the body just delegates to self.stack_effect(oparg), so ForIter will report +1 on both edges. Either:

  • match Self::ForIter (and any other affected ops) explicitly here, or
  • drop the misleading example from the doc-comment until the override lands.

Given the related call-site bug at line 122, callers depending on jump-edge effects will compound the miscount. Worth resolving before this is consumed by the codegen/CFG-flow logic that uses stack_effect_jump.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 49 - 58, The
doc-comment promises that stack_effect_jump differs for certain ops like
Self::ForIter, but stack_effect_jump currently just delegates to
stack_effect(oparg), so ForIter's branch effect isn't handled; update
stack_effect_jump to match on instruction variants (e.g., match self {
Self::ForIter => -1, /* other special cases */ ... , _ =>
self.stack_effect(oparg) }) so branch (jump=true) effects are correct, or
alternatively remove the misleading ForIter example from the doc-comment if you
do not want to implement overrides now; ensure callers that rely on
stack_effect_jump (the call-site noted in the review) will receive correct
jump-edge counts.

Comment on lines +121 to 124
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect(oparg)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Instruction::stack_effect_jump calls the wrong delegate.

This forwards to Opcode::stack_effect, not Opcode::stack_effect_jump. PseudoInstruction::stack_effect_jump (line 151-153) correctly forwards to stack_effect_jump. Today the two Opcode methods return identical values, but the moment Opcode::stack_effect_jump is overridden for branch-vs-fallthrough cases (e.g. ForIter, as called out in the doc-comment at lines 49-55), the Instruction path will silently produce wrong stack effects on the branch edge — exactly the contract this method is meant to model.

🐛 Proposed fix
     #[must_use]
     pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
-        self.as_opcode().stack_effect(oparg)
+        self.as_opcode().stack_effect_jump(oparg)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect(oparg)
}
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect_jump(oparg)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 121 - 124,
Instruction::stack_effect_jump incorrectly delegates to Opcode::stack_effect
instead of Opcode::stack_effect_jump; update the method to call
self.as_opcode().stack_effect_jump(oparg) so it mirrors
PseudoInstruction::stack_effect_jump and correctly uses the branch-specific
stack effect behavior for opcodes like ForIter (use as_opcode(),
stack_effect_jump, and Instruction::stack_effect_jump to locate the change).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feeling like this could be written using macro_rules and embed opcode.toml in the file. not confident though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always revert this PR and do that

@youknowone youknowone merged commit 320355f into RustPython:main May 10, 2026
25 checks passed
@ShaharNaveh ShaharNaveh deleted the autogen-opcodes branch May 14, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants