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
2 changes: 1 addition & 1 deletion docs/unified-bytecode-expansion-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ must still obey the no-mixed-execution rule.
| `ArgumentsObjectDependency` | Activation descriptor and expression scan for real `arguments` object access or call targets; parameter/lexical `arguments` reads, `typeof`, updates, and call targets now route as activation slots | Existing sync IR / arguments-object route | Arguments object lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ArgumentsAccess_DeclinesWithArgumentsDependency"` |
| `ArrowLexicalThisDependency` | Activation descriptor gate for arrow lexical `this` / `new.target` ownership before ordinary sync routing | Existing arrow invocation route | Arrow route lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_OrdinarySyncActivationDescriptorBlockers_DeclineBeforeCompile"` |
| `ClassConstructorActivation` | Activation descriptor gate for class constructor activation outside the admitted simple base constructor route and explicit derived-constructor `super(...)` route with post-super `this` body reads/writes | Constructor route | Constructor boundary lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionConstructCallTests&FullyQualifiedName~Constructor"` |
| `CallDependency` | Direct eval outside the one-argument non-spread eval-identifier boundary, out-of-boundary call-target preparation, complex call arguments, and descriptor-level non-parameter callee calls | Existing sync IR call route | Wider call invocation lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_CallWithComplexTemplateLiteralSubstitution_DeclinesCallDependency"` |
| `CallDependency` | Direct eval outside the one-argument non-spread eval-identifier boundary, out-of-boundary call-target preparation, complex call arguments excluding admitted simple/binary template-literal substitutions, and descriptor-level non-parameter callee calls | Existing sync IR call route | Wider call invocation lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_CallWithComplexComputedKeyObjectArg_DeclinesCallDependency"` |
| `DynamicLookupDependency` | Unresolved identifier loads/stores/typeof/update outside the with-backed dynamic-name path | Existing sync IR / environment lookup route | Dynamic-name lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_DynamicIdentifierLookup_DeclinesWithDynamicLookupDependency"` |
| `PropertyReadBoundaryOutOfScope` | Named/computed property reads outside the admitted activation-resolved boundaries | Existing sync IR property route | Property read widening lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ComputedPropertyReadOutsideFirstBoundary_DeclinesWithBoundaryCode"` |
| `PropertyWriteDependency` | Property writes and compound/logical property writes outside the admitted direct property-write shapes, supported computed expression-key mutation shapes, simple nested named receiver assignment shape, nested named compound-write shape, and nested named logical-write shape | Existing sync IR property-write route | Property write widening lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_LogicalAndAssignment_UnsupportedShapes_DeclineWithExplicitCodes"` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8819,7 +8819,44 @@ private static bool TryAppendSimpleTemplateLiteralSpan(
continue;
}

// Substitution part: simple-operand, ToString, Binary(Add)
// Substitution part: simple binary expression, ToString, Binary(Add)
if (i + 4 < expressionProgram.OperationCount)
{
var rightOperand = expressionProgram.GetOperation(i + 1);
var binary = expressionProgram.GetOperation(i + 2);
var toString = expressionProgram.GetOperation(i + 3);
var add = expressionProgram.GetOperation(i + 4);
if (binary.Kind == ExpressionOpKind.Binary &&
IsSupportedBinaryOperator(binary.Operator) &&
toString.Kind == ExpressionOpKind.ToString &&
add.Kind == ExpressionOpKind.Binary &&
add.Operator == BinaryOperator.Add &&
CanAppendSimpleOperandLoad(op, expressionProgram, activationSlots) &&
CanAppendSimpleOperandLoad(rightOperand, expressionProgram, activationSlots))
{
TryAppendSimpleOperandLoad(
op,
expressionProgram,
activationSlots,
unified,
literalConstants,
out _);
TryAppendSimpleOperandLoad(
rightOperand,
expressionProgram,
activationSlots,
unified,
literalConstants,
out _);
Comment on lines +8837 to +8850
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.Binary, (int)binary.Operator));
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.ToString));
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.Binary, (int)BinaryOperator.Add));
i += 5;
continue;
}
}

// Substitution part: simple operand, ToString, Binary(Add)
if (i + 2 < expressionProgram.OperationCount)
{
var toString = expressionProgram.GetOperation(i + 1);
Expand All @@ -8845,6 +8882,23 @@ private static bool TryAppendSimpleTemplateLiteralSpan(
return true;
}

private static bool CanAppendSimpleOperandLoad(
PackedExpressionOp operation,
ExpressionProgram expressionProgram,
ActivationSlotShape activationSlots)
{
return operation.Kind switch
{
ExpressionOpKind.LoadIdentifier => !operation.IsArguments &&
TryResolveActivationSlot(
operation.GetIdentifier(expressionProgram.IdentifierConstants.AsSpan()),
activationSlots,
out _),
ExpressionOpKind.LoadLiteral or ExpressionOpKind.LoadThis or ExpressionOpKind.LoadNewTarget => true,
_ => false
};
}

private static bool TryAppendSimpleOperandLoad(
PackedExpressionOp operation,
ExpressionProgram expressionProgram,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3654,7 +3654,27 @@ private static bool TryMeasureSimpleTemplateLiteralSpan(
break;
}

// Substitution part: simple-operand, ToString, Binary(Add)
// Substitution part: simple binary expression, ToString, Binary(Add)
if (i + 4 < program.OperationCount &&
IsSimpleOperand(op, identifierConstants, activationSlots))
{
var rightOperand = program.GetOperation(i + 1);
var binary = program.GetOperation(i + 2);
var toString = program.GetOperation(i + 3);
var add = program.GetOperation(i + 4);
if (IsSimpleOperand(rightOperand, identifierConstants, activationSlots) &&
binary.Kind == ExpressionOpKind.Binary &&
IsProductionBinaryOperator(binary.Operator) &&
toString.Kind == ExpressionOpKind.ToString &&
add.Kind == ExpressionOpKind.Binary &&
add.Operator == BinaryOperator.Add)
{
i += 5;
continue;
}
}

// Substitution part: simple operand, ToString, Binary(Add)
if (IsSimpleOperand(op, identifierConstants, activationSlots))
{
if (i + 2 < program.OperationCount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5223,7 +5223,7 @@ function logFull(receiver, first, last) {
}

[Fact]
public void Evaluate_CallWithComplexTemplateLiteralSubstitution_DeclinesCallDependency()
public void Evaluate_CallWithBinaryTemplateLiteralSubstitution_Accepts()
{
var plan = GetFunctionPlan("""
function logExpr(receiver, a, b) {
Expand All @@ -5236,8 +5236,14 @@ function logExpr(receiver, a, b) {
plan,
new UnifiedBytecodeProductionActivationDescriptor());

Assert.False(result.IsEligible);
Assert.Equal(UnifiedBytecodeProductionDeclineCode.CallDependency, result.Code);
Assert.True(result.IsEligible, result.Reason);
Assert.Equal(UnifiedBytecodeProductionDeclineCode.None, result.Code);
Assert.Contains(result.Program.Instructions, instruction =>
instruction.OpCode == UnifiedBytecodeOpCode.Binary && instruction.Operand == (int)BinaryOperator.Add);
Assert.Contains(result.Program.Instructions, instruction =>
instruction.OpCode == UnifiedBytecodeOpCode.ToString);
Assert.Contains(result.Program.Instructions, instruction =>
instruction.OpCode == UnifiedBytecodeOpCode.CallInvocationBoundary);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6594,6 +6594,25 @@ static record => record.Message.Contains(
StringComparison.Ordinal));
}

[Fact(Timeout = 5000)]
public async Task CallWithBinaryTemplateLiteralSubstitution_UsesUnifiedBytecodeProductionFastPath()
{
await using var engine = CreateEngine();
var result = await engine.Evaluate("""
function logExpr(receiver, a, b) {
return receiver(`result: ${a + b}`);
}

logExpr(function(value) { return value; }, 40, 2);
""");

Assert.Equal("result: 42", result?.ToString());
Assert.Contains(CurrentLogger!.Collector.Snapshot(),
static record => record.Message.Contains(
"unified-bytecode-production-fast-path func=logExpr argc=3",
StringComparison.Ordinal));
}

[Fact(Timeout = 5000)]
public async Task ObjectSpreadGetterThrow_StopsBeforeLaterSpread_OnFastPath()
{
Expand Down