Skip to content

Commit 5454052

Browse files
Basic byte-code emission
1 parent 47190b0 commit 5454052

File tree

5 files changed

+69
-5
lines changed

5 files changed

+69
-5
lines changed

lib/Parser/Parse.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3910,6 +3910,7 @@ ParseNodePtr Parser::ParsePostfixOperators(
39103910
*pfIsDotOrIndex = false;
39113911
}
39123912

3913+
bool isOptionalChain = false;
39133914
for (;;)
39143915
{
39153916
uint16 spreadArgCount = 0;
@@ -4054,7 +4055,7 @@ ParseNodePtr Parser::ParsePostfixOperators(
40544055
}
40554056
if (pfCanAssign)
40564057
{
4057-
*pfCanAssign = !isNullPropagating && fCanAssignToCallResult &&
4058+
*pfCanAssign = !isOptionalChain && fCanAssignToCallResult &&
40584059
(m_sourceContextInfo ?
40594060
!PHASE_ON_RAW(Js::EarlyErrorOnAssignToCallPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) :
40604061
!PHASE_ON1(Js::EarlyErrorOnAssignToCallPhase));
@@ -4102,7 +4103,7 @@ ParseNodePtr Parser::ParsePostfixOperators(
41024103
if (pfCanAssign)
41034104
{
41044105
// optional assignment not permitted
4105-
*pfCanAssign = !isNullPropagating;
4106+
*pfCanAssign = !isOptionalChain;
41064107
}
41074108
if (pfIsDotOrIndex)
41084109
{
@@ -4192,6 +4193,10 @@ ParseNodePtr Parser::ParsePostfixOperators(
41924193
OpCode opCode = knopDot;
41934194

41944195
bool isNullPropagating = tkOptChain == m_token.tk;
4196+
if (isNullPropagating)
4197+
{
4198+
isOptionalChain = true;
4199+
}
41954200
// We don't use a custom token but rather tell that knopDot is null-propagating
41964201
// opCode = knopOptChain;
41974202

@@ -4220,7 +4225,7 @@ ParseNodePtr Parser::ParsePostfixOperators(
42204225

42214226
if (buildAST)
42224227
{
4223-
if (opCode == knopDot || opCode == knopOptChain)
4228+
if (opCode == knopDot)
42244229
{
42254230
name = CreateNameNode(m_token.GetIdentifier(this->GetHashTbl()));
42264231
}
@@ -4253,7 +4258,7 @@ ParseNodePtr Parser::ParsePostfixOperators(
42534258
if (pfCanAssign)
42544259
{
42554260
// optional assignment not permitted
4256-
*pfCanAssign = !isNullPropagating;
4261+
*pfCanAssign = !isOptionalChain;
42574262
}
42584263
if (pfIsDotOrIndex)
42594264
{
@@ -4295,6 +4300,11 @@ ParseNodePtr Parser::ParsePostfixOperators(
42954300
break;
42964301
}
42974302
default:
4303+
if (isOptionalChain)
4304+
{
4305+
// Wrap as optional chain
4306+
return CreateUniNode(knopOptChain, pnode);
4307+
}
42984308
return pnode;
42994309
}
43004310
}

lib/Parser/ptlist.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ PTNODE(knopGe , ">=" , OP(Ge) , Bin , fnopBin|fn
8181
PTNODE(knopGt , ">" , OP(Gt) , Bin , fnopBin|fnopRel , "GreaterThanOper" )
8282
PTNODE(knopCall , "()" , Nop , Call , fnopNone , "CallExpr" )
8383
PTNODE(knopDot , "." , Nop , Bin , fnopBin , "DotOper" )
84-
PTNODE(knopOptChain , "?." , Nop , Bin , fnopBin , "OptChain" )
84+
PTNODE(knopOptChain , "?." , Nop , Uni , fnopUni , "OptChain" )
8585
PTNODE(knopAsg , "=" , Nop , Bin , fnopBin|fnopAsg , "AssignmentOper" )
8686
PTNODE(knopInstOf , "instanceof" , IsInst , Bin , fnopBin|fnopRel , "InstanceOfExpr" )
8787
PTNODE(knopIn , "in" , IsIn , Bin , fnopBin|fnopRel , "InOper" )

lib/Runtime/ByteCode/ByteCodeEmitter.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ void EmitUseBeforeDeclaration(Symbol *sym, ByteCodeGenerator *byteCodeGenerator,
2020
void EmitUseBeforeDeclarationRuntimeError(ByteCodeGenerator *byteCodeGenerator, Js::RegSlot location);
2121
void VisitClearTmpRegs(ParseNode * pnode, ByteCodeGenerator * byteCodeGenerator, FuncInfo * funcInfo);
2222

23+
/**
24+
* This function generates the common code for null-propagation / optional-chaining.
25+
* This works similar to how the c# compiler emits byte-code for optional-chaining.
26+
*/
27+
static void EmitNullPropagation(Js::RegSlot targetObjectSlot, ByteCodeGenerator *byteCodeGenerator, FuncInfo* funcInfo, bool isNullPropagating) {
28+
if (!isNullPropagating)
29+
return;
30+
31+
Assert(funcInfo->currentOptionalChain != 0);
32+
33+
Js::ByteCodeLabel continueLabel = byteCodeGenerator->Writer()->DefineLabel();
34+
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, continueLabel, targetObjectSlot); // if (targetObject)
35+
{
36+
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, funcInfo->currentOptionalChain->resultSlot); // result = undefined
37+
byteCodeGenerator->Writer()->Br(funcInfo->currentOptionalChain->skipLabel);
38+
}
39+
byteCodeGenerator->Writer()->MarkLabel(continueLabel);
40+
}
41+
2342
bool CallTargetIsArray(ParseNode *pnode)
2443
{
2544
return pnode->nop == knopName && pnode->AsParseNodeName()->PropertyIdFromNameNode() == Js::PropertyIds::Array;
@@ -11596,12 +11615,35 @@ void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* func
1159611615
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
1159711616
funcInfo->AcquireLoc(pnode);
1159811617

11618+
EmitNullPropagation(callObjLocation, byteCodeGenerator, funcInfo, pnode->AsParseNodeBin()->isNullPropagating);
11619+
1159911620
byteCodeGenerator->Writer()->Element(
1160011621
Js::OpCode::LdElemI_A, pnode->location, protoLocation, pnode->AsParseNodeBin()->pnode2->location);
1160111622

11623+
1160211624
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
1160311625
break;
1160411626
}
11627+
11628+
case knopOptChain: {
11629+
Js::ByteCodeLabel skipLabel = byteCodeGenerator->Writer()->DefineLabel();
11630+
Js::RegSlot targetRegSlot = funcInfo->AcquireLoc(pnode);
11631+
11632+
FuncInfo::OptionalChainInfo* previousChain = funcInfo->currentOptionalChain;
11633+
FuncInfo::OptionalChainInfo currentOptionalChain = FuncInfo::OptionalChainInfo(skipLabel, targetRegSlot);
11634+
funcInfo->currentOptionalChain = &currentOptionalChain;
11635+
11636+
ParseNodePtr innerNode = pnode->AsParseNodeUni()->pnode1;
11637+
Emit(innerNode, byteCodeGenerator, funcInfo, false);
11638+
11639+
// Copy result
11640+
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, targetRegSlot, innerNode->location);
11641+
funcInfo->ReleaseLoc(innerNode);
11642+
11643+
byteCodeGenerator->Writer()->MarkLabel(skipLabel);
11644+
funcInfo->currentOptionalChain = previousChain;
11645+
break;
11646+
}
1160511647
// this is MemberExpression as rvalue
1160611648
case knopDot:
1160711649
{
@@ -11622,6 +11664,8 @@ void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* func
1162211664
Js::PropertyId propertyId = pnode->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
1162311665
uint cacheId = funcInfo->FindOrAddInlineCacheId(protoLocation, propertyId, false, false);
1162411666

11667+
EmitNullPropagation(callObjLocation, byteCodeGenerator, funcInfo, pnode->AsParseNodeBin()->isNullPropagating);
11668+
1162511669
if (propertyId == Js::PropertyIds::length)
1162611670
{
1162711671
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdLen_A, pnode->location, protoLocation, cacheId);

lib/Runtime/ByteCode/FuncInfo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ FuncInfo::FuncInfo(
3333
outArgsCurrentExpr(0),
3434
innerScopeCount(0),
3535
currentInnerScopeIndex((uint)-1),
36+
currentOptionalChain(nullptr),
3637
#if DBG
3738
outArgsDepth(0),
3839
#endif

lib/Runtime/ByteCode/FuncInfo.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ class FuncInfo
9595
Js::RegSlot outArgsCurrentExpr; // max number of out args accumulated in the current nested expression
9696
uint innerScopeCount;
9797
uint currentInnerScopeIndex;
98+
struct OptionalChainInfo {
99+
Js::ByteCodeLabel skipLabel;
100+
Js::RegSlot resultSlot;
101+
102+
OptionalChainInfo(Js::ByteCodeLabel skipLabel, Js::RegSlot resultSlot) {
103+
this->skipLabel = skipLabel;
104+
this->resultSlot = resultSlot;
105+
}
106+
} *currentOptionalChain;
98107
#if DBG
99108
uint32 outArgsDepth; // number of calls nested in an expression
100109
#endif

0 commit comments

Comments
 (0)