Skip to content
Closed
Next Next commit
Sort Instruction enum. Variants hold 1 element
  • Loading branch information
ShaharNaveh committed Nov 29, 2025
commit 423fc3db24041722974c017c640f304a18ef04b3
351 changes: 117 additions & 234 deletions crates/compiler-core/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,263 +549,146 @@ pub type NameIdx = u32;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum Instruction {
Nop,
/// Importing by name
ImportName {
idx: Arg<NameIdx>,
},
/// Importing without name
ImportNameless,
/// from ... import ...
ImportFrom {
idx: Arg<NameIdx>,
},
LoadFast(Arg<NameIdx>),
LoadNameAny(Arg<NameIdx>),
LoadGlobal(Arg<NameIdx>),
LoadDeref(Arg<NameIdx>),
LoadClassDeref(Arg<NameIdx>),
StoreFast(Arg<NameIdx>),
StoreLocal(Arg<NameIdx>),
StoreGlobal(Arg<NameIdx>),
StoreDeref(Arg<NameIdx>),
BeforeAsyncWith,
BinaryOperation(Arg<BinaryOperator>),
BinaryOperationInplace(Arg<BinaryOperator>),
BinarySubscript,
Break(Arg<Label>),
BuildList(Arg<u32>),
BuildListFromTuples(Arg<u32>),
BuildMap(Arg<u32>),
BuildMapForCall(Arg<u32>),
BuildSet(Arg<u32>),
BuildSetFromTuples(Arg<u32>),
/// When holds `true` it will build a slice with a third step argument.
BuildSlice(Arg<bool>),
BuildString(Arg<u32>),
BuildTuple(Arg<u32>),
BuildTupleFromIter,
BuildTupleFromTuples(Arg<u32>),
CallFunctionEx(Arg<bool>),
CallFunctionKeyword(Arg<u32>),
CallFunctionPositional(Arg<u32>),
CallIntrinsic1(Arg<IntrinsicFunction1>),
CallIntrinsic2(Arg<IntrinsicFunction2>),
CallMethodEx(Arg<bool>),
CallMethodKeyword(Arg<u32>),
CallMethodPositional(Arg<u32>),
CompareOperation(Arg<ComparisonOperator>),
/// Performs `in` comparison, or `not in` if `invert` is 1.
ContainsOp(Arg<Invert>),
Continue(Arg<Label>),
CopyItem(Arg<u32>),
DeleteAttr(Arg<NameIdx>),
DeleteDeref(Arg<NameIdx>),
DeleteFast(Arg<NameIdx>),
DeleteLocal(Arg<NameIdx>),
DeleteGlobal(Arg<NameIdx>),
DeleteDeref(Arg<NameIdx>),
LoadClosure(Arg<NameIdx>),
Subscript,
StoreSubscript,
DeleteLocal(Arg<NameIdx>),
DeleteSubscript,
/// Performs `is` comparison, or `is not` if `invert` is 1.
IsOp(Arg<Invert>),
/// Performs `in` comparison, or `not in` if `invert` is 1.
ContainsOp(Arg<Invert>),
StoreAttr {
idx: Arg<NameIdx>,
},
DeleteAttr {
idx: Arg<NameIdx>,
},
LoadConst {
/// index into constants vec
idx: Arg<u32>,
},
UnaryOperation {
op: Arg<UnaryOperator>,
},
BinaryOperation {
op: Arg<BinaryOperator>,
},
BinaryOperationInplace {
op: Arg<BinaryOperator>,
},
BinarySubscript,
LoadAttr {
idx: Arg<NameIdx>,
},
CompareOperation {
op: Arg<ComparisonOperator>,
},
CopyItem {
index: Arg<u32>,
},
Pop,
Swap {
index: Arg<u32>,
},
ToBool,
DictUpdate(Arg<u32>),
EndAsyncFor,
/// Marker bytecode for the end of a finally sequence.
/// When this bytecode is executed, the eval loop does one of those things:
/// - Continue at a certain bytecode position
/// - Propagate the exception
/// - Return from a function
/// - Do nothing at all, just continue
EndFinally,
/// Enter a finally block, without returning, excepting, just because we are there.
EnterFinally,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ExtendedArg,
ForIter(Arg<Label>),
FormatValue(Arg<ConversionFlag>),
GetAIter,
GetANext,
GetAwaitable,
GetIter,
GetLen,
CallIntrinsic1 {
func: Arg<IntrinsicFunction1>,
},
CallIntrinsic2 {
func: Arg<IntrinsicFunction2>,
},
Continue {
target: Arg<Label>,
},
Break {
target: Arg<Label>,
},
/// from ... import ...
ImportFrom(Arg<NameIdx>),
/// Importing by name
ImportName(Arg<NameIdx>),
/// Importing without name
ImportNameless,
/// Performs `is` comparison, or `is not` if `invert` is 1.
IsOp(Arg<Invert>),
Jump(Arg<Label>),
/// Peek at the top of the stack, and jump if this value is false.
/// Otherwise, pop top of stack.
JumpIfFalseOrPop(Arg<Label>),
/// Performs exception matching for except.
/// Tests whether the STACK[-2] is an exception matching STACK[-1].
/// Pops STACK[-1] and pushes the boolean result of the test.
JumpIfNotExcMatch(Arg<Label>),
Jump {
target: Arg<Label>,
},
/// Pop the top of the stack, and jump if this value is true.
PopJumpIfTrue {
target: Arg<Label>,
},
/// Pop the top of the stack, and jump if this value is false.
PopJumpIfFalse {
target: Arg<Label>,
},
/// Peek at the top of the stack, and jump if this value is true.
/// Otherwise, pop top of stack.
JumpIfTrueOrPop {
target: Arg<Label>,
},
/// Peek at the top of the stack, and jump if this value is false.
/// Otherwise, pop top of stack.
JumpIfFalseOrPop {
target: Arg<Label>,
},
JumpIfTrueOrPop(Arg<Label>),
ListAppend(Arg<u32>),
LoadAttr(Arg<NameIdx>),
LoadBuildClass,
LoadClassDeref(Arg<NameIdx>),
LoadClosure(Arg<NameIdx>),
/// Holds an index into constants vec.
LoadConst(Arg<u32>),
LoadDeref(Arg<NameIdx>),
LoadFast(Arg<NameIdx>),
LoadGlobal(Arg<NameIdx>),
LoadMethod(Arg<NameIdx>),
LoadNameAny(Arg<NameIdx>),
MakeFunction,
SetFunctionAttribute {
attr: Arg<MakeFunctionFlags>,
},
CallFunctionPositional {
nargs: Arg<u32>,
},
CallFunctionKeyword {
nargs: Arg<u32>,
},
CallFunctionEx {
has_kwargs: Arg<bool>,
},
LoadMethod {
idx: Arg<NameIdx>,
},
CallMethodPositional {
nargs: Arg<u32>,
},
CallMethodKeyword {
nargs: Arg<u32>,
},
CallMethodEx {
has_kwargs: Arg<bool>,
},
ForIter {
target: Arg<Label>,
},
ReturnValue,
ReturnConst {
idx: Arg<u32>,
},
YieldValue,
YieldFrom,

MapAdd(Arg<u32>),
MatchClass(Arg<u32>),
MatchKeys,
MatchMapping,
MatchSequence,
Nop,
Pop,
PopBlock,
PopException,
/// Pop the top of the stack, and jump if this value is false.
PopJumpIfFalse(Arg<Label>),
/// Pop the top of the stack, and jump if this value is true.
PopJumpIfTrue(Arg<Label>),
PrintExpr,
Raise(Arg<RaiseKind>),
/// Resume execution (e.g., at function start, after yield, etc.)
Resume {
arg: Arg<u32>,
},

Resume(Arg<u32>),
ReturnConst(Arg<u32>),
ReturnValue,
Reverse(Arg<u32>),
SetAdd(Arg<u32>),
SetFunctionAttribute(Arg<MakeFunctionFlags>),
SetupAnnotation,
SetupLoop,

SetupAsyncWith(Arg<Label>),
SetupExcept(Arg<Label>),
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.

how about at least keeping the name as comment?

e.g.

Suggested change
SetupExcept(Arg<Label>),
SetupExcept(Arg<Label>), // handler

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.

Sure, I can do that

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.

Could you also tell me about the motivation to remove the label? They compiled to the same bytecode, but having label is easier to read the code

Copy link
Copy Markdown
Contributor Author

@ShaharNaveh ShaharNaveh Nov 29, 2025

Choose a reason for hiding this comment

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

The motivation is the inconsistenty between opcodes that holds the same OpArgType, some holds it as an associated value and some as a named field, which is a bit confusing imo. also, I find it very tedious and just more boilerplate for having different an enum that it's variants hold nothing or a single named field.

I'm not so good with words so here's what I mean:

I can justify having enums like this where different variants hold different fields, even if some overlapping:

enum Foo {
    A { name: String, age: u8 },
    B { name: String, job: String, married: bool },
    ...
}

But I do find it redundant and overly verbose to have something like this:

enum Foo {
    A { name: String },
    B { name: String },
    C { name: String },
    ...
}

Not to mention that we have cases like this:

enum Foo {
    A { target: Label },
    B { end: Label },
    C { handler: Label },
    ...
}

Because Label is a newtype for u32 already, it's just confusing as to why the same newtype is being "treated" differently. I'd much rather to go with newtyprs all the way like:

enum Foo {
    A(TargetLabel),
    B(EndLabel),
    C(HandlerLabel),
    ...
}

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.

@youknowone I can revert that change if that's a deal breaker for you:)

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.

I like the new enum idea on #6313. Not sure about Label. Because all the labels are actually label.

If the consistency matters, giving label for every instruction looks better to me. It feels like redundancy while writing code, but we read code more than writing code. Getting information from code is easier than checking document.

The situation is also a bit different to your example. It is more like:

enum Foo {
    CallMethodPositional { nargs: String },
    ReturnConst { idx: String },
    ...
}

Maybe giving different types for them will also make sense though.

cc @coolreader18

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.

I like the new enum idea on #6313. Not sure about Label. Because all the labels are actually label.

If the consistency matters, giving label for every instruction looks better to me. It feels like redundancy while writing code, but we read code more than writing code. Getting information from code is easier than checking document.

IMO consistency matters, I can make them consistent in the other way around by having each Oparg to be stored as a named field if that's desired.

The situation is also a bit different to your example. It is more like:

enum Foo {
    CallMethodPositional { nargs: String },
    ReturnConst { idx: String },
    ...
}

Maybe giving different types for them will also make sense though.

I think that it will, but that is out of scope for this PR, I'll do it in a separate one

cc @coolreader18

/// Setup a finally handler, which will be called whenever one of this events occurs:
/// - the block is popped
/// - the function returns
/// - an exception is returned
SetupFinally {
handler: Arg<Label>,
},

/// Enter a finally block, without returning, excepting, just because we are there.
EnterFinally,

/// Marker bytecode for the end of a finally sequence.
/// When this bytecode is executed, the eval loop does one of those things:
/// - Continue at a certain bytecode position
/// - Propagate the exception
/// - Return from a function
/// - Do nothing at all, just continue
EndFinally,

SetupExcept {
handler: Arg<Label>,
},
SetupWith {
end: Arg<Label>,
},
WithCleanupStart,
SetupFinally(Arg<Label>),
SetupLoop,
SetupWith(Arg<Label>),
StoreAttr(Arg<NameIdx>),
StoreDeref(Arg<NameIdx>),
StoreFast(Arg<NameIdx>),
StoreGlobal(Arg<NameIdx>),
StoreLocal(Arg<NameIdx>),
StoreSubscript,
Subscript,
Swap(Arg<u32>),
ToBool,
UnaryOperation(Arg<UnaryOperator>),
UnpackEx(Arg<UnpackExArgs>),
UnpackSequence(Arg<u32>),
WithCleanupFinish,
PopBlock,
Raise {
kind: Arg<RaiseKind>,
},
BuildString {
size: Arg<u32>,
},
BuildTuple {
size: Arg<u32>,
},
BuildTupleFromTuples {
size: Arg<u32>,
},
BuildTupleFromIter,
BuildList {
size: Arg<u32>,
},
BuildListFromTuples {
size: Arg<u32>,
},
BuildSet {
size: Arg<u32>,
},
BuildSetFromTuples {
size: Arg<u32>,
},
BuildMap {
size: Arg<u32>,
},
BuildMapForCall {
size: Arg<u32>,
},
DictUpdate {
index: Arg<u32>,
},
BuildSlice {
/// whether build a slice with a third step argument
step: Arg<bool>,
},
ListAppend {
i: Arg<u32>,
},
SetAdd {
i: Arg<u32>,
},
MapAdd {
i: Arg<u32>,
},

PrintExpr,
LoadBuildClass,
UnpackSequence {
size: Arg<u32>,
},
UnpackEx {
args: Arg<UnpackExArgs>,
},
FormatValue {
conversion: Arg<ConversionFlag>,
},
PopException,
Reverse {
amount: Arg<u32>,
},
GetAwaitable,
BeforeAsyncWith,
SetupAsyncWith {
end: Arg<Label>,
},
GetAIter,
GetANext,
EndAsyncFor,
MatchMapping,
MatchSequence,
MatchKeys,
MatchClass(Arg<u32>),
ExtendedArg,
WithCleanupStart,
YieldFrom,
YieldValue,
// If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated
}

// This must be kept up to date to avoid marshaling errors
const LAST_INSTRUCTION: Instruction = Instruction::ExtendedArg;
const LAST_INSTRUCTION: Instruction = Instruction::YieldValue;

const _: () = assert!(mem::size_of::<Instruction>() == 1);

Expand Down