Skip to content

Commit d2a0104

Browse files
authored
Admit member call spread sources (#3025)
1 parent 9ecc6d3 commit d2a0104

5 files changed

Lines changed: 375 additions & 24 deletions

File tree

docs/unified-bytecode-expansion-contract.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ must still obey the no-mixed-execution rule.
336336
| `DeleteDependency` | `delete` expressions outside the admitted ordinary named/computed property delete lane and the with-backed dynamic-name delete lane | Existing sync IR delete route | Delete semantics lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_PropertyReadAdjacentFamilies_DeclineWithExplicitCodes"` |
337337
| `SuperPropertyDependency` | Out-of-boundary super call targets; super property reads/writes/updates are admitted by dedicated VM opcodes | Existing class / constructor route for remaining call-target shapes | Super semantics lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_SuperPropertyAccess_AcceptsOwnedOpcodes"` |
338338
| `OptionalChainDependency` | Optional chains outside the admitted optional property-read, optional-call, and exact optional named/computed delete boundaries | Existing sync IR optional-chain route | Optional-chain widening lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_OptionalChainNamedPrefixPlainCallExpressionPlan_AcceptsExecutableInvocationBoundary"` |
339-
| `ObjectLiteralOrSpreadDependency` | Non-simple object spread sources, unsupported array spread, and object methods/accessors only when they appear inside restricted simple literal spans | Existing sync IR literal/spread route for remaining spans | Literal/spread lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_NonSimpleSourceArraySpread_DeclinesWithExplicitCode"` |
339+
| `ObjectLiteralOrSpreadDependency` | Object/array spread sources outside simple operands and direct named member-call spread sources, and object methods/accessors only when they appear inside restricted simple literal spans | Existing sync IR literal/spread route for remaining spans | Literal/spread lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ObjectLiteralUnsupportedFeatures_DeclineWithExplicitCodes"` |
340340
| `PrivateFieldDependency` | Private-name operations outside the admitted routes; `#name in obj`, direct private named reads/writes/updates, direct private named compound/logical writes, and direct private named method calls are VM-owned when the surrounding class method is otherwise production-eligible. Private member deletes are parser early errors before production eligibility. | Existing private-name route for remaining private member access | Private-name lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_PrivateFieldIn_AcceptsAndVmChecksPrivateBrand"` |
341341
| `ForInDriverStateDependency` | Unsupported for-in driver state such as awaited object source | Existing for-in IR driver route | Driver-state lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~IsSupportedForInInit_AwaitedSource_Declines"` |
342342
| `DestructuringDependency` | Binding declarations with defaults, computed binding names, assignment targets, awaited binding values, unsupported destructuring driver shapes, and targets outside the admitted driver or descriptor-backed lanes | Existing destructuring IR route for remaining shapes | Destructuring driver lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_UnsupportedDestructuringDriverShapes_DeclineWithExplicitReason"` |

src/Asynkron.JsEngine/Execution/UnifiedBytecode/UnifiedBytecodeCompiler.cs

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6053,7 +6053,7 @@ private static bool TryAppendCallArguments(
60536053
{
60546054
if (!TryAppendSimpleArrayLiteralSpan(
60556055
expressionProgram, operationIndex, activationSlots,
6056-
unified, literalConstants, out var arraySpanLen, out reason))
6056+
unified, literalConstants, stringConstants, callTargetConstants, slotLayout, out var arraySpanLen, out reason))
60576057
{
60586058
return false;
60596059
}
@@ -6151,6 +6151,9 @@ private static bool TryAppendSimpleArrayLiteralSpan(
61516151
ActivationSlotShape activationSlots,
61526152
ImmutableArray<UnifiedBytecodeInstruction>.Builder unified,
61536153
ImmutableArray<JsValue>.Builder literalConstants,
6154+
ImmutableArray<string>.Builder stringConstants,
6155+
ImmutableArray<UnifiedBytecodeCallTarget>.Builder? callTargetConstants,
6156+
UnifiedBytecodeSlotLayout? slotLayout,
61546157
out int spanLength,
61556158
out string reason)
61566159
{
@@ -6175,14 +6178,44 @@ private static bool TryAppendSimpleArrayLiteralSpan(
61756178
continue;
61766179
}
61776180

6178-
if (!TryAppendSimpleOperandLoad(elementOp, expressionProgram, activationSlots, unified, literalConstants, out reason))
6181+
if (slotLayout is not null &&
6182+
callTargetConstants is not null &&
6183+
TryMeasureSimpleDirectNamedCallOperandSpan(
6184+
expressionProgram,
6185+
i,
6186+
activationSlots,
6187+
out _,
6188+
out _,
6189+
out var callElementSpanLength))
6190+
{
6191+
if (!TryAppendSimpleDirectNamedCallOperandSpan(
6192+
expressionProgram,
6193+
i,
6194+
activationSlots,
6195+
slotLayout,
6196+
unified,
6197+
literalConstants,
6198+
stringConstants,
6199+
callTargetConstants,
6200+
out reason))
6201+
{
6202+
spanLength = 0;
6203+
return false;
6204+
}
6205+
6206+
i += callElementSpanLength;
6207+
}
6208+
else if (TryAppendSimpleOperandLoad(elementOp, expressionProgram, activationSlots, unified, literalConstants, out reason))
6209+
{
6210+
i++;
6211+
}
6212+
else
61796213
{
61806214
// Non-simple op — element scan is done; the array literal ends here.
61816215
// Undo the failed load (TryAppendSimpleOperandLoad adds nothing on failure).
61826216
break;
61836217
}
61846218

6185-
i++;
61866219
if (i >= expressionProgram.OperationCount)
61876220
{
61886221
spanLength = 0;
@@ -6373,6 +6406,38 @@ callTargetConstants is not null &&
63736406
}
63746407

63756408
var firstOp = expressionProgram.GetOperation(i);
6409+
if (slotLayout is not null &&
6410+
callTargetConstants is not null &&
6411+
TryMeasureSimpleDirectNamedCallOperandSpan(
6412+
expressionProgram,
6413+
i,
6414+
activationSlots,
6415+
out _,
6416+
out _,
6417+
out var spreadCallSpanLength) &&
6418+
i + spreadCallSpanLength < expressionProgram.OperationCount &&
6419+
expressionProgram.GetOperation(i + spreadCallSpanLength).Kind == ExpressionOpKind.ObjectSpread)
6420+
{
6421+
if (!TryAppendSimpleDirectNamedCallOperandSpan(
6422+
expressionProgram,
6423+
i,
6424+
activationSlots,
6425+
slotLayout,
6426+
unified,
6427+
literalConstants,
6428+
stringConstants,
6429+
callTargetConstants,
6430+
out reason))
6431+
{
6432+
spanLength = 0;
6433+
return false;
6434+
}
6435+
6436+
unified.Add(new UnifiedBytecodeInstruction(UnifiedBytecodeOpCode.ObjectSpread));
6437+
i += spreadCallSpanLength + 1;
6438+
continue;
6439+
}
6440+
63766441
if (!TryAppendSimpleOperandLoad(firstOp, expressionProgram, activationSlots, unified, literalConstants, out reason))
63776442
{
63786443
// Non-simple first op — property scan is done; the object literal ends here.
@@ -6544,6 +6609,131 @@ private static bool TryAppendSimpleIdentifierCallOperandSpan(
65446609
return true;
65456610
}
65466611

6612+
private static bool TryMeasureSimpleDirectNamedCallOperandSpan(
6613+
ExpressionProgram expressionProgram,
6614+
int startIndex,
6615+
ActivationSlotShape activationSlots,
6616+
out int callIndex,
6617+
out int argumentCount,
6618+
out int spanLength)
6619+
{
6620+
callIndex = -1;
6621+
argumentCount = 0;
6622+
spanLength = 0;
6623+
if (startIndex + 2 >= expressionProgram.OperationCount)
6624+
{
6625+
return false;
6626+
}
6627+
6628+
if (!CanAppendSimpleOperandLoad(expressionProgram.GetOperation(startIndex), expressionProgram, activationSlots))
6629+
{
6630+
return false;
6631+
}
6632+
6633+
var callTarget = expressionProgram.GetOperation(startIndex + 1);
6634+
if (callTarget.Kind != ExpressionOpKind.LoadNamedCallTarget ||
6635+
callTarget.IsOptional ||
6636+
callTarget.ShortCircuitOnNullishTarget ||
6637+
callTarget.GetString(expressionProgram.StringConstants.AsSpan()).IsPrivateName())
6638+
{
6639+
return false;
6640+
}
6641+
6642+
var operationIndex = startIndex + 2;
6643+
while (operationIndex < expressionProgram.OperationCount &&
6644+
CanAppendSimpleOperandLoad(expressionProgram.GetOperation(operationIndex), expressionProgram, activationSlots))
6645+
{
6646+
argumentCount++;
6647+
operationIndex++;
6648+
}
6649+
6650+
if (operationIndex >= expressionProgram.OperationCount)
6651+
{
6652+
return false;
6653+
}
6654+
6655+
var call = expressionProgram.GetOperation(operationIndex);
6656+
if (call.Kind != ExpressionOpKind.Call ||
6657+
!call.HasExplicitThis ||
6658+
call.IsDirectEval ||
6659+
call.SpreadMaskConstantIndex >= 0 ||
6660+
call.ArgumentCount != argumentCount)
6661+
{
6662+
argumentCount = 0;
6663+
return false;
6664+
}
6665+
6666+
callIndex = operationIndex;
6667+
spanLength = operationIndex - startIndex + 1;
6668+
return true;
6669+
}
6670+
6671+
private static bool TryAppendSimpleDirectNamedCallOperandSpan(
6672+
ExpressionProgram expressionProgram,
6673+
int startIndex,
6674+
ActivationSlotShape activationSlots,
6675+
UnifiedBytecodeSlotLayout slotLayout,
6676+
ImmutableArray<UnifiedBytecodeInstruction>.Builder unified,
6677+
ImmutableArray<JsValue>.Builder literalConstants,
6678+
ImmutableArray<string>.Builder stringConstants,
6679+
ImmutableArray<UnifiedBytecodeCallTarget>.Builder callTargetConstants,
6680+
out string reason)
6681+
{
6682+
if (!TryMeasureSimpleDirectNamedCallOperandSpan(
6683+
expressionProgram,
6684+
startIndex,
6685+
activationSlots,
6686+
out var callIndex,
6687+
out var argumentCount,
6688+
out _))
6689+
{
6690+
reason = "Spread sources only admit direct named member calls with simple arguments.";
6691+
return false;
6692+
}
6693+
6694+
if (!TryAppendSimpleOperandLoad(
6695+
expressionProgram.GetOperation(startIndex),
6696+
expressionProgram,
6697+
activationSlots,
6698+
unified,
6699+
literalConstants,
6700+
out reason))
6701+
{
6702+
return false;
6703+
}
6704+
6705+
var callTarget = expressionProgram.GetOperation(startIndex + 1);
6706+
var callTargetNameIndex = stringConstants.Count;
6707+
stringConstants.Add(callTarget.GetString(expressionProgram.StringConstants.AsSpan()));
6708+
var callTargetConstantIndex = callTargetConstants.Count;
6709+
callTargetConstants.Add(new UnifiedBytecodeCallTarget(
6710+
UnifiedBytecodeCallTargetKind.NamedMember,
6711+
NameConstantIndex: callTargetNameIndex));
6712+
unified.Add(new UnifiedBytecodeInstruction(
6713+
UnifiedBytecodeOpCode.PrepareNamedCallTarget,
6714+
callTargetConstantIndex));
6715+
6716+
for (var operationIndex = startIndex + 2; operationIndex < callIndex; operationIndex++)
6717+
{
6718+
if (!TryAppendSimpleOperandLoad(
6719+
expressionProgram.GetOperation(operationIndex),
6720+
expressionProgram,
6721+
activationSlots,
6722+
unified,
6723+
literalConstants,
6724+
out reason))
6725+
{
6726+
return false;
6727+
}
6728+
}
6729+
6730+
unified.Add(new UnifiedBytecodeInstruction(
6731+
UnifiedBytecodeOpCode.CallInvocationBoundary,
6732+
EncodeCallBoundaryOperand(argumentCount, spreadMaskIndex: -1, isDirectEval: false)));
6733+
reason = string.Empty;
6734+
return true;
6735+
}
6736+
65476737
private static bool TryMeasureSimpleObjectLiteralSpan(
65486738
ExpressionProgram expressionProgram,
65496739
int startIndex,
@@ -9076,7 +9266,8 @@ private static bool TryAppendFirstBoundaryPropertyReadBinaryExpression(
90769266
if (rhsOp.Kind == ExpressionOpKind.CreateArray)
90779267
{
90789268
if (!TryAppendSimpleArrayLiteralSpan(
9079-
expressionProgram, rhsStart, activationSlots, unified, literalConstants,
9269+
expressionProgram, rhsStart, activationSlots, unified, literalConstants, stringConstants,
9270+
callTargetConstants: null, slotLayout: null,
90809271
out var arraySpanLen, out reason) ||
90819272
rhsStart + arraySpanLen - 1 != rhsEnd)
90829273
{

0 commit comments

Comments
 (0)