Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions docs/unified-bytecode-expansion-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,19 @@ statement interpretation.
- Expression lowering is still the largest surface. `ExpressionOpKind` contains
more shapes than the general unified lowering loop accepts. Several operations
are admitted only through narrow shape helpers, while private property
reads/writes/updates, optional-chain short-circuit flag handling, and
some call/reference helpers remain outside general unified bytecode lowering.
reads/writes/updates, remaining optional-call chain shapes, and some
call/reference helpers remain outside general unified bytecode lowering.
- The remaining direct general-expression lowering gaps are drift-checked under
`### General Expression Lowering Gaps (current)`. Property set/update
operations plus the `SwapTopTwo`, `DuplicateTopTwo`, and
`RotateTopThreeRight` stack operations are no longer helper-only compiler
shapes; they lower through the general expression loop, subject to the same
private-name and name-inference semantic guards as the owned property-write
helpers.
- Optional-chain short-circuit provenance is now VM-owned for the general
expression loop. `JumpIfShortCircuited` lowers directly to unified bytecode,
and the VM tracks a packed side flag per operand-stack slot so short-circuited
`undefined` remains distinct from ordinary `undefined`.
- The current `ExpressionOpKind` names with no unified compiler reference list is
empty. Computed super-property operations also route their required
`EnsureSuperReference` op through the general unified expression loop.
Expand Down Expand Up @@ -197,6 +201,7 @@ statement interpretation.
- `JumpIfShortCircuitFalse`
- `JumpIfShortCircuitTrue`
- `JumpIfShortCircuitNotNullish`
- `JumpIfShortCircuited`
- `JumpIfNullishReplaceUndefined`
*(Note: `Jump` was already in the inventory; `ConditionalExpression` (`?:`) is now admitted via ADR 0297 using the existing `Jump`, `JumpIfShortCircuitFalse`, and `Pop` opcodes without new additions.)*
- `Return`
Expand Down Expand Up @@ -366,7 +371,7 @@ the final post-compile production subset check before VM entry.
- Stateful iterator, for-in, and array destructuring driver operations

### General Expression Lowering Gaps (current)
- `JumpIfShortCircuited`
- None.

## Production This-Binding Boundary
- Ordinary sync functions that reference `this` are admitted to the production
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ public static bool TryCompile(
return false;
}

var compiledInstructions = unified.ToImmutable();
program = new UnifiedBytecodeProgram(
unified.ToImmutable(),
compiledInstructions,
maxStackDepth,
slotLayout.SlotCount,
literalConstants.ToImmutable(),
Expand Down Expand Up @@ -198,11 +199,26 @@ public static bool TryCompile(
: bindingTargetConstants.ToImmutable(),
templateObjectConstants.Count == 0
? ImmutableArray<TaggedTemplateDescriptor>.Empty
: templateObjectConstants.ToImmutable());
: templateObjectConstants.ToImmutable(),
RequiresShortCircuitStackFlags(compiledInstructions));
reason = string.Empty;
return true;
}

private static bool RequiresShortCircuitStackFlags(
ImmutableArray<UnifiedBytecodeInstruction> instructions)
{
foreach (var instruction in instructions)
{
if (instruction.OpCode == UnifiedBytecodeOpCode.JumpIfShortCircuited)
{
return true;
}
}

return false;
}

private static UnifiedBytecodeProgram EmptyProgram() =>
new(
ImmutableArray<UnifiedBytecodeInstruction>.Empty,
Expand Down Expand Up @@ -4585,6 +4601,12 @@ private static bool TryAppendExpressionProgramOps(
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.JumpIfShortCircuitNotNullish, 0));
break;

case ExpressionOpKind.JumpIfShortCircuited:
patches ??= [];
patches.Add((unified.Count, operation.Target));
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.JumpIfShortCircuited, 0));
break;

case ExpressionOpKind.Jump:
patches ??= [];
patches.Add((unified.Count, operation.Target));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1018,41 +1018,11 @@ private static bool TryFindExpressionDecline(

if (operation.IsOptional)
{
if (TryIsFirstBoundaryOptionalNamedThenComputedPropertyDeleteCandidate(program, identifierConstants, activationSlots))
{
break;
}

if (TryIsEmbeddedOptionalReadOperandOperation(program, operationIndex, identifierConstants, activationSlots))
{
break;
}

// Simple a?.b form — admitted when the program is exactly [activation-resolved base, GetNamedPropertyOptional].
if (TryIsFirstBoundaryOptionalNamedPropertyReadCandidate(program, identifierConstants, activationSlots))
{
break;
}

// a?.b.c / a?.b?.c chain, or a?.b[k] shape.
if (TryIsFirstBoundaryOptionalNamedChainCandidate(program, identifierConstants, activationSlots) ||
TryIsFirstBoundaryOptionalNamedThenComputedReadChainCandidate(program, identifierConstants, activationSlots))
{
break;
}

// a?.b.c() / a?.b?.c() optional call-chain forms (Case 4/5):
// isCallTargetPreparationCandidate is set by TryIsFirstBoundaryCallTargetPreparationCandidate
// which already accepted the program via the optional call-chain candidates.
if (isCallTargetPreparationCandidate)
{
break;
}

declineCode = UnifiedBytecodeProductionDeclineCode.OptionalChainDependency;
declineReason =
"Optional-chain property reads are outside the first production property-read boundary.";
return true;
// Named optional reads now lower through the general expression loop.
// The VM owns the short-circuit provenance bit needed by
// JumpIfShortCircuited, while unsupported adjacent optional-chain
// shapes still decline through their own operation checks.
break;
}

if (isCallTargetPreparationCandidate)
Expand Down Expand Up @@ -1380,22 +1350,7 @@ private static bool TryFindExpressionDecline(
return true;

case ExpressionOpKind.JumpIfShortCircuited:
if (isCallTargetPreparationCandidate)
{
break;
}

// JumpIfShortCircuited only appears in call-target programs; property-read chains
// use GetNamedProperty(ShortCircuitOnNullishTarget:true) instead.
if (TryIsFirstBoundaryOptionalNamedPropertyReadChainCandidate(program, identifierConstants, activationSlots))
{
break;
}

declineCode = UnifiedBytecodeProductionDeclineCode.OptionalChainDependency;
declineReason =
"Optional-chain short-circuiting is outside the first production property-read boundary.";
return true;
break;

case ExpressionOpKind.LoadClassLiteral:
break;
Expand Down Expand Up @@ -4392,6 +4347,7 @@ private static bool TryFindPrototypeOnlyOpcode(
case UnifiedBytecodeOpCode.GetNamedProperty:
case UnifiedBytecodeOpCode.GetNamedPropertyOptional:
case UnifiedBytecodeOpCode.JumpIfNullishReplaceUndefined:
case UnifiedBytecodeOpCode.JumpIfShortCircuited:
case UnifiedBytecodeOpCode.GetComputedProperty:
case UnifiedBytecodeOpCode.GetNamedPropertyForCompoundSet:
case UnifiedBytecodeOpCode.GetComputedPropertyForCompoundSet:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ internal enum UnifiedBytecodeOpCode : byte
JumpIfShortCircuitFalse,
JumpIfShortCircuitTrue,
JumpIfShortCircuitNotNullish,
JumpIfShortCircuited,
JumpIfNullishReplaceUndefined,
Return,
ReturnUndefined,
Expand Down Expand Up @@ -302,4 +303,5 @@ internal sealed record UnifiedBytecodeProgram(
ImmutableArray<ClassExpression> ClassLiteralConstants = default,
ImmutableArray<ClassDeclarationDescriptor> ClassDeclarationConstants = default,
ImmutableArray<BindingTargetProgram> BindingTargetConstants = default,
ImmutableArray<TaggedTemplateDescriptor> TemplateObjectConstants = default);
ImmutableArray<TaggedTemplateDescriptor> TemplateObjectConstants = default,
bool RequiresShortCircuitStackFlags = false);
Loading
Loading