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
4 changes: 2 additions & 2 deletions docs/unified-bytecode-expansion-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ must still obey the no-mixed-execution rule.
| `AsyncLikeFunction` | `Evaluate` activation gate and awaited with-object plan decline | Existing async / awaited IR route | Resumable async/generator lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_AsyncLikeActivation_DeclinesBeforePlanInspection"` |
| `GeneratorFunction` | `Evaluate` activation gate for ordinary sync production routing | Existing generator IR route | Resumable async/generator lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_GeneratorActivation_DeclinesBeforePlanInspection"` |
| `CapturedOrDynamicActivation` | Activation descriptor `HasCapturedOrDynamicActivation`, including captured function scope or unresolved dynamic activation outside the with-backed lane | Existing sync IR / environment route | Dynamic activation lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ActivationDependencies_DeclineBeforeCompile"` |
| `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"` |
| `ArgumentsObjectDependency` | Activation descriptor and expression scan for real `arguments` object access or call targets; implicit `typeof arguments` routes as a literal `"object"`, while parameter/lexical `arguments` reads, `typeof`, updates, and call targets 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 excluding admitted simple/binary template-literal substitutions, simple/binary computed object keys, and zero-argument activation-resolved identifier-call computed object keys, 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_ActivationDependencies_DeclineBeforeCompile"` |
Expand Down Expand Up @@ -360,7 +360,7 @@ not always have a `UnifiedBytecodeProductionDeclineCode`.
| `pre-gate:IsDefaultDerivedConstructor` | Default derived class constructor setup | Constructor route | Constructor boundary lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionInvocationTests"` |
| `pre-gate:hasParameterExpressions` | Default/rest/destructured parameter expressions | Existing parameter environment route | Parameter semantics lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionInvocationTests"` |
| `pre-gate:hasOnlySimpleIdentifierParameters` | Non-simple parameter lists | Existing parameter environment route | Parameter semantics lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionInvocationTests"` |
| `pre-gate:usesArguments` | Functions that read the real implicit `arguments` object; shadowing parameter/lexical `arguments` reads, `typeof`, updates, and calls are activation-slot traffic | Existing arguments-object route | Arguments object lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ArgumentsAccess_DeclinesWithArgumentsDependency"` |
| `pre-gate:usesArguments` | Functions that read the real implicit `arguments` object except the bounded `typeof arguments` literal route; shadowing parameter/lexical `arguments` reads, `typeof`, updates, and calls are activation-slot traffic | Existing arguments-object route | Arguments object lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ArgumentsAccess_DeclinesWithArgumentsDependency"` |
| `pre-gate:needsArgumentsBinding` | Sloppy mapped arguments binding when an implicit arguments object is needed and not admitted by with-backed dynamic-name routing | Existing arguments-object route | Arguments object lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionInvocationTests"` |
| `pre-gate:allowIdentifierCache` | Identifier cache disabled outside the ordinary and with-backed dynamic-name lanes | Existing environment lookup route | Dynamic-name lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_WithThenOutsideDynamicIdentifier_DeclinesWithDynamicLookupDependency"` |
| `pre-gate:lexicalThisEnvironment` | Lexical `this` environment captured by arrow / class-derived shapes | Existing lexical-this route | This-binding lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionInvocationTests"` |
Expand Down
21 changes: 18 additions & 3 deletions src/Asynkron.JsEngine/Ast/TypedAstEvaluator.SyncFunctionInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3312,7 +3312,8 @@ private bool TryGetProductionUnifiedBytecodeProgram(
CanUseProductionUnifiedBytecodeOrdinaryDynamicNameFastPath(plan),
CanUseProductionUnifiedBytecodeArrowFunctionActivation(plan, newTarget),
CanUseProductionUnifiedBytecodeDerivedClassConstructorActivation(plan, newTarget),
CanUseProductionUnifiedBytecodeBaseClassConstructorActivation(plan, newTarget)));
CanUseProductionUnifiedBytecodeBaseClassConstructorActivation(plan, newTarget),
CanUseProductionUnifiedBytecodeTypeOfImplicitArgumentsPath(plan)));

if (!result.IsEligible && IsPlanStructuralDecline(result.Code))
{
Expand Down Expand Up @@ -3361,12 +3362,15 @@ private bool CanUseProductionUnifiedBytecodeFastPath(ExecutionPlan plan, JsValue
CanUseProductionUnifiedBytecodeDerivedClassConstructorActivation(plan, newTarget);
var canUseBaseClassConstructorPath =
CanUseProductionUnifiedBytecodeBaseClassConstructorActivation(plan, newTarget);
var canUseTypeOfImplicitArgumentsPath =
CanUseProductionUnifiedBytecodeTypeOfImplicitArgumentsPath(plan);
var activation = CreateProductionUnifiedBytecodeActivationDescriptor(
canUseDynamicNamePath,
canUseOrdinaryDynamicNamePath,
canUseArrowFunctionPath,
canUseDerivedClassConstructorPath,
canUseBaseClassConstructorPath);
canUseBaseClassConstructorPath,
canUseTypeOfImplicitArgumentsPath);
if (UnifiedBytecodeProductionEligibility.TryFindOrdinarySyncActivationDecline(
activation,
out _,
Expand Down Expand Up @@ -3400,7 +3404,8 @@ private UnifiedBytecodeProductionActivationDescriptor CreateProductionUnifiedByt
bool canUseOrdinaryDynamicNamePath,
bool canUseArrowFunctionPath = false,
bool canUseDerivedClassConstructorPath = false,
bool canUseBaseClassConstructorPath = false)
bool canUseBaseClassConstructorPath = false,
bool canUseTypeOfImplicitArgumentsPath = false)
{
var hasUnprovenDynamicActivation = !_allowIdentifierCache &&
!canUseDynamicNamePath &&
Expand All @@ -3416,6 +3421,7 @@ private UnifiedBytecodeProductionActivationDescriptor CreateProductionUnifiedByt
HasArgumentsObjectDependency:
!_hasDirectEvalInBodyOrParameters &&
_argumentsObjectNeeded &&
!canUseTypeOfImplicitArgumentsPath &&
(_usesArguments || _needsArgumentsBinding && !canUseDynamicNamePath),
HasArrowLexicalThisDependency:
IsArrowFunction && !canUseArrowFunctionPath || _lexicalThisEnvironment is not null,
Expand Down Expand Up @@ -3933,6 +3939,15 @@ private bool CanUseProductionUnifiedBytecodeOrdinaryDynamicNameFastPath(Executio
UnifiedBytecodeProductionEligibility.ContainsOrdinaryDynamicIdentifierDependency(plan);
}

private bool CanUseProductionUnifiedBytecodeTypeOfImplicitArgumentsPath(ExecutionPlan plan)
{
return _usesArguments &&
!_hasDirectEvalInBodyOrParameters &&
!_hasClosureWithObject &&
!_hasCapturedActivationInClosure &&
UnifiedBytecodeProductionEligibility.ContainsOnlyTypeOfImplicitArgumentsObjectDependency(plan);
}

private bool CanUseProductionUnifiedBytecodePlanShape(
ExecutionPlan plan,
bool canUseDynamicNamePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4207,7 +4207,12 @@ private static bool TryAppendExpressionProgramOps(
{
if (operation.IsArguments)
{
return false;
var argumentsTypeIndex = literalConstants.Count;
literalConstants.Add(new JsValue("object"));
unified.Add(new UnifiedBytecodeInstruction(
UnifiedBytecodeOpCode.LoadLiteral,
argumentsTypeIndex));
break;
}

var typeOfIdentifier = operation.GetIdentifier(expressionProgram.IdentifierConstants.AsSpan());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,36 @@ private static UnifiedBytecodeProgram EmptyProgram() =>

internal static class UnifiedBytecodeProductionEligibility
{
internal static bool ContainsOnlyTypeOfImplicitArgumentsObjectDependency(ExecutionPlan plan)
{
var foundTypeOfArguments = false;
for (var instructionIndex = 0; instructionIndex < plan.Instructions.Length; instructionIndex++)
{
if (!TryGetExpressionProgram(plan.Instructions[instructionIndex], out var program))
{
continue;
}

for (var operationIndex = 0; operationIndex < program.OperationCount; operationIndex++)
{
var operation = program.GetOperation(operationIndex);
if (!operation.IsArguments)
{
continue;
}

if (operation.Kind != ExpressionOpKind.TypeOfIdentifier)
{
return false;
}

foundTypeOfArguments = true;
}
}

return foundTypeOfArguments;
}

internal static bool ContainsOrdinaryDynamicIdentifierDependency(ExecutionPlan plan)
{
if (plan.ActivationSlots is not { } activationSlots ||
Expand Down Expand Up @@ -990,10 +1020,7 @@ private static bool TryFindExpressionDecline(
var hasTypeOfActivationSlot = TryResolveActivationSlot(typeOfIdentifier, activationSlots);
if (operation.IsArguments && !hasTypeOfActivationSlot)
{
declineCode = UnifiedBytecodeProductionDeclineCode.ArgumentsObjectDependency;
declineReason =
"arguments object access is not eligible for production unified bytecode routing.";
return true;
break;
}

if (!hasTypeOfActivationSlot)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3057,7 +3057,7 @@ function readShadow(arguments) {
}

[Fact]
public void Evaluate_TypeOfImplicitArgumentsObject_DeclinesWithArgumentsDependency()
public void Evaluate_TypeOfImplicitArgumentsObject_AcceptsLiteralObjectType()
{
var plan = GetFunctionPlan("""
function readArguments() {
Expand All @@ -3070,8 +3070,9 @@ function readArguments() {
plan,
new UnifiedBytecodeProductionActivationDescriptor());

Assert.False(result.IsEligible);
Assert.Equal(UnifiedBytecodeProductionDeclineCode.ArgumentsObjectDependency, result.Code);
Assert.True(result.IsEligible, result.Reason);
Assert.Equal(UnifiedBytecodeProductionDeclineCode.None, result.Code);
Assert.Contains(result.Program.LiteralConstants, value => value.AsString() == "object");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5843,7 +5843,7 @@ record => record.Message.Contains(
}

[Fact(Timeout = 5000)]
public async Task TypeOfImplicitArgumentsObject_DeclinesAndFallsBack()
public async Task TypeOfImplicitArgumentsObject_UsesUnifiedBytecodeProductionFastPath()
{
await using var engine = CreateEngine();
var result = await engine.Evaluate("""
Expand All @@ -5855,9 +5855,9 @@ function readArguments() {
""");

Assert.Equal("object", result);
Assert.DoesNotContain(CurrentLogger!.Collector.Snapshot(),
Assert.Contains(CurrentLogger!.Collector.Snapshot(),
record => record.Message.Contains(
"unified-bytecode-production-fast-path func=readArguments",
"unified-bytecode-production-fast-path func=readArguments argc=1",
StringComparison.Ordinal));
}

Expand Down