@@ -20,18 +20,23 @@ void EmitUseBeforeDeclaration(Symbol *sym, ByteCodeGenerator *byteCodeGenerator,
20
20
void EmitUseBeforeDeclarationRuntimeError (ByteCodeGenerator *byteCodeGenerator, Js::RegSlot location);
21
21
void VisitClearTmpRegs (ParseNode * pnode, ByteCodeGenerator * byteCodeGenerator, FuncInfo * funcInfo);
22
22
23
- /* *
24
- * This function generates the common code for null-propagation / optional-chaining.
25
- */
23
+ // / <summary>
24
+ // / This function generates the common code for null-propagation / optional-chaining.
25
+ // / If the targetObject is nullish this will short-circuit(skip) to the end of the chain-expression.
26
+ // /
27
+ // / It should be called on every <c>?.</c> location.
28
+ // / A call to this function is only valid from a node-emission inside a `knopOptChain` node.
29
+ // / </summary>
26
30
static void EmitNullPropagation (Js::RegSlot targetObjectSlot, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, bool isNullPropagating) {
27
31
if (!isNullPropagating)
28
32
return ;
29
33
30
- Assert (funcInfo->currentOptionalChain != 0 );
34
+ // Ensure we've setup the skipLabel in the emission of a `knopOptChain`
35
+ Assert (funcInfo->currentOptionalChainSkipLabel >= 0 );
31
36
32
- // if (targetObject == null) goto chainEnd ;
37
+ // if (targetObject == null) goto skipLabel ;
33
38
byteCodeGenerator->Writer ()->BrReg2 (
34
- Js::OpCode::BrEq_A, funcInfo->currentOptionalChain -> skipLabel ,
39
+ Js::OpCode::BrEq_A, funcInfo->currentOptionalChainSkipLabel ,
35
40
targetObjectSlot, funcInfo->nullConstantRegister
36
41
);
37
42
}
@@ -11621,31 +11626,35 @@ void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* func
11621
11626
byteCodeGenerator->Writer ()->Element (
11622
11627
Js::OpCode::LdElemI_A, pnode->location , protoLocation, pnode->AsParseNodeBin ()->pnode2 ->location );
11623
11628
11624
-
11625
11629
ENDSTATEMENET_IFTOPLEVEL (isTopLevel, pnode);
11626
11630
break ;
11627
11631
}
11628
-
11632
+ // The whole optional-chain expression will be wrapped in a UniNode with `knopOptChain`.
11629
11633
case knopOptChain: {
11630
- FuncInfo::OptionalChainInfo *previousChain = funcInfo->currentOptionalChain ;
11634
+ Js::ByteCodeLabel previousSkipLabel = funcInfo->currentOptionalChainSkipLabel ;
11631
11635
11636
+ // Create a label that can skip the whole chain and store in `funcInfo`
11632
11637
Js::ByteCodeLabel skipLabel = byteCodeGenerator->Writer ()->DefineLabel ();
11633
- FuncInfo::OptionalChainInfo currentOptionalChain = FuncInfo::OptionalChainInfo (skipLabel);
11634
- funcInfo->currentOptionalChain = ¤tOptionalChain;
11638
+ funcInfo->currentOptionalChainSkipLabel = skipLabel;
11635
11639
11640
+ // Acquire slot for the result value
11641
+ // Prefill it with `undefined` (Fallback for short-circuiting)
11636
11642
Js::RegSlot resultSlot = funcInfo->AcquireLoc (pnode);
11637
- byteCodeGenerator->Writer ()->Reg1 (Js::OpCode::LdUndef, resultSlot); // result = undefined
11643
+ byteCodeGenerator->Writer ()->Reg1 (Js::OpCode::LdUndef, resultSlot);
11638
11644
11639
- // emit chain
11645
+ // emit chain expression
11646
+ // Every `?.` node will call `EmitNullPropagation`
11647
+ // `EmitNullPropagation` short-circuits to `skipLabel` in case of a nullish value
11640
11648
ParseNodePtr innerNode = pnode->AsParseNodeUni ()->pnode1 ;
11641
11649
Emit (innerNode, byteCodeGenerator, funcInfo, false );
11642
11650
11643
- // Copy result
11651
+ // Copy the expression result
11652
+ // Only reached if we did not short-circuit
11644
11653
byteCodeGenerator->Writer ()->Reg2 (Js::OpCode::Ld_A, resultSlot, innerNode->location );
11645
11654
funcInfo->ReleaseLoc (innerNode);
11646
11655
11647
11656
byteCodeGenerator->Writer ()->MarkLabel (skipLabel);
11648
- funcInfo->currentOptionalChain = previousChain ;
11657
+ funcInfo->currentOptionalChainSkipLabel = previousSkipLabel ;
11649
11658
break ;
11650
11659
}
11651
11660
// this is MemberExpression as rvalue
0 commit comments