Skip to content

refactor(compile): single-source ILEmitter/state-machine Expr.Get static dispatch (drift fix)#1273

Merged
nickna merged 1 commit into
mainfrom
worktree-emitcall-drift
Jul 11, 2026
Merged

refactor(compile): single-source ILEmitter/state-machine Expr.Get static dispatch (drift fix)#1273
nickna merged 1 commit into
mainfrom
worktree-emitcall-drift

Conversation

@nickna

@nickna nickna commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Round 8a: closes the highest-risk item left from the July audits — the actively-diverging duplicate of the Expr.Get static-dispatch blocks.

Problem

ILEmitter.EmitCall re-inlined the module.promises.method() and Class.staticMethod() / inherited-Promise-static dispatch that TryEmitGetCalleeViaBaseClass also implements. The copies had already drifted twice:

Change

  • Extracted TryEmitModulePromisesMethodCall + TryEmitClassStaticDispatch into ExpressionEmitterBase.CallHelpers; both TryEmitGetCalleeViaBaseClass and ILEmitter.EmitCall now route through them.
  • The boxing divergence is now an explicit, documented seam: protected virtual EnsureBoxedArg(Expr) defaults to stack-state EnsureBoxed() (state-machine behavior unchanged); ILEmitter overrides it with its richer TypeMap/literal-aware EmitBoxIfNeeded (its behavior unchanged). Both paths emit their exact prior IL — this is a dedup, not a behavior change.
  • The suspension-spill branch is unreachable from ILEmitter (sync bodies contain no await/yield), so sharing it is inert there.

Tests

  • New StaticDispatchBoxingTests (3 theories × interpreter/compiled): boxing-sensitive static args from a sync top-level call site (ILEmitter path), from inside an async function (state-machine path), and Promise-subclass statics from async code.
  • Existing PromiseSubclassTests.ExtendsPromise_TopLevelStaticThen already pins the historical MyP.resolve(1) regression.
  • Full suite: 15458 passed / 0 failed. EmitterSyncTests pass (no allowlist changes needed — the new virtual lives on the base, and only ILEmitter overrides it).

…shared by ILEmitter and the state-machine base

ILEmitter.EmitCall re-inlined the module.promises and
Class.staticMethod/Promise-subclass-static dispatch that
TryEmitGetCalleeViaBaseClass also implements, and the copies were
actively drifting: the base gained the #439 await-safe argument spill
the inline copy lacked, and the inline copy boxed extra args via
EmitBoxIfNeeded while the base used EnsureBoxed. An earlier drift of
this exact block shipped a bug (MyP.resolve(1) threw).

Extract TryEmitModulePromisesMethodCall and TryEmitClassStaticDispatch
into ExpressionEmitterBase.CallHelpers and route both callers through
them. The boxing divergence becomes an explicit named seam:
EnsureBoxedArg defaults to stack-state EnsureBoxed (state-machine
behavior, unchanged) and ILEmitter overrides it with its TypeMap/
literal-aware EmitBoxIfNeeded (unchanged) — both paths keep their
exact prior IL. The suspension-spill branch is unreachable from
ILEmitter (sync bodies contain no await/yield).

Adds StaticDispatchBoxingTests pinning boxing-sensitive static
dispatch from sync and async call sites plus the Promise-subclass
statics from async code, in both execution modes.
@nickna
nickna merged commit 45f0265 into main Jul 11, 2026
2 checks passed
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.

1 participant