diff --git a/docs/unified-bytecode-expansion-contract.md b/docs/unified-bytecode-expansion-contract.md index 05b9c05ee..1495a98e1 100644 --- a/docs/unified-bytecode-expansion-contract.md +++ b/docs/unified-bytecode-expansion-contract.md @@ -435,10 +435,10 @@ the final post-compile production subset check before VM entry. `box.child?.items[key]`), with the prefix emitted as owned `GetNamedProperty` reads before the optional jump-to-chain-end boundary. Nested named receiver chains ending in a simple - computed delete (`delete box.child[key]`) are admitted by - `TryIsFirstBoundaryComputedPropertyDeleteCandidate`; optional named deletes - (`delete box?.value`, `delete box.child?.value`) and optional computed delete - chains (`delete box?.[key]`, `delete box?.child[key]`, and + computed delete (`delete box.child[key]`, `delete box.child[left + right]`) + are admitted by `TryIsFirstBoundaryComputedPropertyDeleteCandidate`; optional + named deletes (`delete box?.value`, `delete box.child?.value`) and optional + computed delete chains (`delete box?.[key]`, `delete box?.child[key]`, and `delete box.child?.[key]`) are admitted for activation-resolved receivers, supported computed-key spans, and compiler-owned nullish short-circuit-to-true lowering (ADR 0317 plus the optional delete follow-ups). The compiler emits @@ -447,7 +447,7 @@ the final post-compile production subset check before VM entry. strict/sloppy results. Retained declines include chained optional delete neighbors, private receiver-chain/mutation neighbors outside the admitted direct named - shape, dynamic lookup, richer unowned computed-key spans, unsupported RHS + shape, dynamic lookup, unsupported computed-key spans, unsupported RHS spans, and optional/super/private neighbors of computed-key mutation shapes. Private member deletes are rejected before this production gate. Note: slot-identifier logical assignment (`x &&= y`) remains admitted. diff --git a/src/Asynkron.JsEngine/Execution/UnifiedBytecode/UnifiedBytecodeProductionEligibility.cs b/src/Asynkron.JsEngine/Execution/UnifiedBytecode/UnifiedBytecodeProductionEligibility.cs index db9c54b96..8fceeb7c7 100644 --- a/src/Asynkron.JsEngine/Execution/UnifiedBytecode/UnifiedBytecodeProductionEligibility.cs +++ b/src/Asynkron.JsEngine/Execution/UnifiedBytecode/UnifiedBytecodeProductionEligibility.cs @@ -1965,20 +1965,26 @@ private static bool TryIsFirstBoundaryComputedPropertyDeleteCandidate( } var stringConstants = program.StringConstants.AsSpan(); - var keyIndex = program.OperationCount - 2; - for (var index = 1; index < keyIndex; index++) + var keyStartIndex = 1; + for (; keyStartIndex < program.OperationCount - 1; keyStartIndex++) { - var operation = program.GetOperation(index); + var operation = program.GetOperation(keyStartIndex); if (operation.Kind != ExpressionOpKind.GetNamedProperty || operation.GetString(stringConstants).IsPrivateName() || operation.IsOptional || operation.ShortCircuitOnNullishTarget) { - return false; + break; } } - return IsSimpleComputedPropertyKeyOperand(program.GetOperation(keyIndex), identifierConstants, activationSlots) && + return keyStartIndex < program.OperationCount - 1 && + IsSupportedComputedPropertyKeySpan( + program, + startInclusive: keyStartIndex, + endExclusive: program.OperationCount - 1, + identifierConstants, + activationSlots) && program.GetOperation(program.OperationCount - 1).Kind == ExpressionOpKind.DeleteComputedProperty; } diff --git a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs index 91c3ba05b..304a07787 100644 --- a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs +++ b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs @@ -1390,6 +1390,28 @@ function remove(box, key) { instruction.OpCode == UnifiedBytecodeOpCode.LoadSlot)); } + [Fact] + public void Evaluate_NestedComputedPropertyDeleteRichKeyCandidate_AcceptsOwnedPropertyOpcodes() + { + var plan = GetFunctionPlan(""" + function remove(box, left, right) { + return delete box.child[left + right]; + } + """, + "remove"); + + var result = UnifiedBytecodeProductionEligibility.Evaluate( + plan, + new UnifiedBytecodeProductionActivationDescriptor()); + + Assert.True(result.IsEligible, result.Reason); + Assert.Equal(UnifiedBytecodeProductionDeclineCode.None, result.Code); + Assert.Contains(result.Program.Instructions, instruction => + instruction.OpCode == UnifiedBytecodeOpCode.Binary); + Assert.Contains(result.Program.Instructions, instruction => + instruction.OpCode == UnifiedBytecodeOpCode.DeleteComputedProperty); + } + [Theory] [InlineData( """ @@ -2881,14 +2903,6 @@ function remove(box) { """, "remove", (int)UnifiedBytecodeProductionDeclineCode.DynamicLookupDependency)] - [InlineData( - """ - function remove(box, left, right) { - return delete box.child[left + right]; - } - """, - "remove", - (int)UnifiedBytecodeProductionDeclineCode.DeleteDependency)] public void Evaluate_NestedComputedPropertyDeleteUnsupportedKey_DeclinesBeforeVm( string source, string functionName, diff --git a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs index 9f1930ce6..26b1fcf7e 100644 --- a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs +++ b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs @@ -4672,6 +4672,27 @@ static record => record.Message.Contains( StringComparison.Ordinal)); } + [Fact(Timeout = 5000)] + public async Task NestedNamedReceiverComputedPropertyDeleteRichKey_UsesUnifiedBytecodeProductionFastPath() + { + await using var engine = CreateEngine(); + var result = await engine.Evaluate(""" + function remove(box, left, right) { + return delete box.child[left + right]; + } + + var child = { value: 42 }; + var box = { child: child }; + remove(box, "val", "ue") && !Object.prototype.hasOwnProperty.call(child, "value"); + """); + + Assert.Equal(true, result); + Assert.Contains(CurrentLogger!.Collector.Snapshot(), + static record => record.Message.Contains( + "unified-bytecode-production-fast-path func=remove argc=3", + StringComparison.Ordinal)); + } + [Fact(Timeout = 5000)] public async Task OptionalNamedThenComputedPropertyDelete_UsesUnifiedBytecodeProductionFastPathAndPreservesKeyOrder() {