-
Notifications
You must be signed in to change notification settings - Fork 1
IR Execution
Detailed documentation of the Intermediate Representation (IR) execution system.
File: Execution/Instructions/InstructionKind.cs
Complete instruction taxonomy (40+ types):
internal enum InstructionKind : byte
{
// Statements & Expressions
Statement, // Evaluate statement node
Throw, // Throw expression
EvaluateAndDiscard, // Evaluate expression, discard result
BinaryOp, // Fast-path binary operation
IncrementSlot, // ++/-- on slot variable
CompoundAssignmentSlot, // += -= *= etc on slot
// Variables & Functions
SimpleVariableDeclaration, // let x = expr (no destructuring)
ComplexVariableDeclaration, // let {a, b} = obj (destructuring)
FunctionDeclaration, // function f() {}
ClassDeclaration, // class C {}
// Scopes
PushEnvironment, // Enter new scope
PopEnvironment, // Exit scope
// Control Flow
Jump, // Unconditional jump
Branch, // Conditional jump
Break, // Break statement
Continue, // Continue statement
Return, // Return statement
// Generator/Async
Yield, // yield expression
YieldStar, // yield* expression
StoreResumeValue, // Store .next(value) payload
// Exception Handling
EnterTry, // try { ... }
EnterCatch, // catch (e) { ... }
EnterCatchWithDestructuring, // catch ({x}) { ... }
LeaveTry, // Normal completion of try/catch
EndFinally, // finally { ... }
// Loops
BreakableEnter, // Enter loop/switch
BreakableExit, // Exit loop/switch
IteratorInit, // for-of initialization
IteratorMoveNext, // for-of iteration step
IteratorClose, // for-of cleanup
// For-In Loops
ForInInit, // for-in initialization
ForInMoveNext, // for-in iteration step
// With Statements
EnterWith, // with (obj) { ... }
LeaveWith, // Exit with scope
// Array Destructuring
ArrayDestructuringInit, // Initialize iterator
ArrayDestructuringElement, // Extract element
ArrayDestructuringRest, // Collect rest elements
ArrayDestructuringClose, // Close iterator
// Metadata
SetCompletionValue // Set script completion to undefined
}File: Ast/TypedAstEvaluator.ExecutionPlanRunner.cs
private sealed partial class ExecutionPlanRunner
{
private readonly ExecutionPlan? _plan;
private int _programCounter;
private int _currentInstructionIndex;
// Generator/async state machine
private GeneratorState _state = GeneratorState.Start;
private bool _done;
// Script completion value (ES spec)
private JsValue _scriptCompletionValue = JsValue.Unit;
// Lazy-allocated state (only when needed)
private TryCatchState? _tryCatchState;
private AsyncState AsyncStateRef;
private YieldState YieldStateRef;
private IteratorState IteratorStateRef;
private BreakableState BreakableStateRef;
// Flat slots for O(1) variable access
private JsVariable[]? _flatSlots;
}File: Ast/TypedAstEvaluator.ExecutionPlanRunner.InstructionHandlers.cs
Handlers are stored in a delegate array indexed by InstructionKind:
private delegate InstructionResult InstructionHandler(
ExecutionPlanRunner runner,
ExecutionInstruction instruction,
ref JsEnvironment environment,
EvaluationContext context,
out JsValue returnValue);
private static readonly InstructionHandler[] InstructionHandlers = InitializeHandlers();
private static InstructionHandler[] InitializeHandlers()
{
var handlers = new InstructionHandler[40];
handlers[(int)InstructionKind.Statement] = HandleStatement;
handlers[(int)InstructionKind.Throw] = HandleThrow;
handlers[(int)InstructionKind.BinaryOp] = HandleBinaryOp;
handlers[(int)InstructionKind.IncrementSlot] = HandleIncrementSlot;
handlers[(int)InstructionKind.Jump] = HandleJump;
handlers[(int)InstructionKind.Branch] = HandleBranch;
// ... 34 more handlers
return handlers;
}File: Ast/TypedAstEvaluator.ExecutionPlanRunner.Loop.cs
flowchart TB
Start((Start)) --> Fetch["Fetch instruction at PC"]
Fetch --> CheckJump{Kind == Jump?}
CheckJump -->|Yes| DoJump["PC = target"]
DoJump --> Fetch
CheckJump -->|No| CheckBranch{Kind == Branch?}
CheckBranch -->|Yes| EvalCond["Evaluate condition"]
EvalCond --> SetPC["PC = then/else target"]
SetPC --> Fetch
CheckBranch -->|No| Dispatch["Dispatch table lookup<br/>handlers[kind]"]
Dispatch --> Execute["Execute handler"]
Execute --> CheckResult{Result?}
CheckResult -->|Continue| NextPC["PC = instruction.Next"]
NextPC --> Fetch
CheckResult -->|Return| Done((Return value))
style CheckJump fill:#f9f,stroke:#333
style CheckBranch fill:#f9f,stroke:#333
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private JsValue ExecuteInstructionLoop(ref JsEnvironment environment, EvaluationContext context)
{
var instructions = _plan!.Instructions;
var instructionsLength = instructions.Length;
// Bounds-check-free array access
var instructionsArray = ImmutableCollectionsMarshal.AsArray(instructions)!;
ref var instructionsRef = ref MemoryMarshal.GetArrayDataReference(instructionsArray);
while ((uint)_programCounter < (uint)instructionsLength)
{
_currentInstructionIndex = _programCounter;
var instruction = Unsafe.Add(ref instructionsRef, _programCounter);
var instructionKind = instruction.Kind;
// HOT PATH: Jump (simplest instruction)
if (instructionKind == InstructionKind.Jump)
{
_programCounter = Unsafe.As<JumpInstruction>(instruction).TargetIndex;
continue;
}
// HOT PATH: Branch (conditional jump)
if (instructionKind == InstructionKind.Branch)
{
var result = HandleBranchFastPath(...);
if (result == InstructionResult.Return) return returnValue;
continue;
}
// All other instructions via dispatch table
var loopResult = InstructionHandlers[(int)instructionKind](
this, instruction, ref environment, context, out var loopReturnValue);
if (loopResult == InstructionResult.Return)
{
return loopReturnValue;
}
}
_state = GeneratorState.Completed;
_done = true;
return CreateIteratorResult(JsValue.Undefined, true);
}Fast-path binary operations with async signal handling:
private static InstructionResult HandleBinaryOp(
ExecutionPlanRunner runner,
ExecutionInstruction instr,
ref JsEnvironment environment,
EvaluationContext context,
out JsValue returnValue)
{
var instruction = Unsafe.As<BinaryOpInstruction>(instr);
var binLeft = instruction.Left.EvaluateExpression(environment, context);
if (context.ShouldStopEvaluation)
{
return HandleBinaryOpLeftSlow(...);
}
var binRight = instruction.Right.EvaluateExpression(environment, context);
if (context.ShouldStopEvaluation)
{
return HandleBinaryOpRightSlow(...);
}
var binResult = ApplyBinaryOperator(instruction.Operator, binLeft, binRight, context);
if (instruction.ResultSlot is not null)
{
environment.AssignJsValue(instruction.ResultSlot, binResult);
}
runner._programCounter = instruction.Next;
returnValue = default;
return InstructionResult.Continue;
}private static InstructionResult HandleIncrementSlot(
ExecutionPlanRunner runner,
ExecutionInstruction instr,
ref JsEnvironment environment,
EvaluationContext context,
out JsValue returnValue)
{
var instruction = Unsafe.As<IncrementSlotInstruction>(instr);
var flatSlotId = instruction.FlatSlotId;
// Super-fast path: flat slot with number value
if (flatSlotId >= 0)
{
ref var targetVar = ref runner._flatSlots![flatSlotId];
if (targetVar.IsConst)
{
throw new ThrowSignal(StandardLibrary.CreateTypeError(
$"Assignment to constant variable '{instruction.TargetSymbol.Name}'."));
}
var currentValue = targetVar.Read();
if (currentValue.Kind == JsValueKind.Number)
{
var numValue = currentValue.NumberValue;
var newValue = instruction.IsIncrement ? numValue + 1.0 : numValue - 1.0;
targetVar.Write(newValue);
runner._programCounter = instruction.Next;
returnValue = default;
return InstructionResult.Continue;
}
}
return HandleIncrementSlotSlow(...);
}Files: TryEmitter.cs, ExecutionPlanRunner.cs
flowchart TB
subgraph Try["try block"]
T0["0: EnterTry(catch=3, finally=6)"]
T1["1: Statement(a())"]
T2["2: LeaveTry(6)"]
end
subgraph Catch["catch block"]
C3["3: EnterCatch(e)"]
C4["4: Statement(b())"]
C5["5: Jump(8)"]
end
subgraph Finally["finally block"]
F6["6: StartFinally"]
F7["7: Statement(c())"]
F8["8: EndFinally"]
end
T0 --> T1
T1 -->|"normal"| T2
T1 -.->|"throw"| C3
T2 --> F6
C3 --> C4
C4 --> C5
C5 --> F8
F6 --> F7
F7 --> F8
F8 --> Next((Continue))
catch (ThrowSignal signal)
{
if (context.IsThrow)
{
context.Clear();
}
if (HandleAbruptCompletion(AbruptKind.Throw, signal.ThrownValue))
{
// Jump to catch block, continue execution
if (_programCounter == _currentInstructionIndex)
{
_programCounter = _currentInstructionIndex + 1;
}
continueAfterCatch = true;
continue;
}
// No handler - mark complete and re-throw
_state = GeneratorState.Completed;
_done = true;
throw;
}| Field | Purpose |
|---|---|
_programCounter |
Current instruction index (jump target) |
_currentInstructionIndex |
Instruction being executed (for try/finally) |
Control Flow:
-
Normal:
_programCounter = instruction.Next -
Jump:
_programCounter = targetIndex -
Branch:
_programCounter = consequentIndex or alternateIndex - Return/Throw: Abrupt completion routing
stateDiagram-v2
[*] --> Start : Create generator
Start --> Executing : .next() called
Executing --> Suspended : yield
Suspended --> Executing : .next(value)
Suspended --> Completed : .return(value)
Executing --> Completed : return / end
Suspended --> Executing : .throw(error)
Completed --> [*]
note right of Start : Never executed yet
note right of Suspended : Paused at yield
enum GeneratorState
{
Start, // Never executed
Executing, // Currently running
Suspended, // Yielded, awaiting resume
Completed // Done
}
enum ResumeMode
{
Next, // iterator.next(value)
Return, // iterator.return(value)
Throw // iterator.throw(error)
}Jump and Branch checked before dispatch table lookup.
Uses Unsafe.Add and MemoryMarshal for bounds-check-free array access.
O(1) variable access for loop counters via direct array indexing.
Try/catch, async, yield state only allocated when needed.
Handlers marked with [MethodImpl(JsEngineConstants.Inlining)].
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static InstructionResult HandleFoo(...)
{
if (fastCondition)
{
// ~30 lines, inlined into hot loop
return InstructionResult.Continue;
}
return HandleFooSlow(...); // NoInlining, doesn't bloat loop
}File: Execution/ExecutionPlan.cs
internal sealed record ExecutionPlan(
ImmutableArray<ExecutionInstruction> Instructions,
int EntryPoint,
int SlotCount = 0,
ImmutableArray<Symbol> SlotSymbols = default,
int RootSlotCount = 0,
ImmutableDictionary<Symbol, int>? RootSlotMap = null,
ImmutableHashSet<Symbol>? RootLexicalBindings = null,
int RootScopeId = 0,
int LayoutId = 0,
int FlatSlotCount = 0,
ImmutableDictionary<int, ImmutableArray<(int SlotIndex, int FlatSlotId)>>? FlatSlotMappings = null
);- Architecture Overview - High-level execution model
- JsEnvironment & Slots - Variable storage
- Generators & Async - Generator IR mechanics
- Performance Patterns - Optimization strategies