blob: 57837e275e41e0b9374351fdbd8ec5f4f6b1fc5f [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at */
#ifndef frontend_BytecodeEmitter_h
#define frontend_BytecodeEmitter_h
* JS bytecode generation.
#include "jscntxt.h"
#include "jsopcode.h"
#include "jsscript.h"
#include "frontend/ParseMaps.h"
#include "frontend/Parser.h"
#include "frontend/SharedContext.h"
#include "frontend/SourceNotes.h"
namespace js {
class ScopeObject;
namespace frontend {
class FullParseHandler;
class ObjectBox;
class ParseNode;
template <typename ParseHandler> class Parser;
class SharedContext;
class TokenStream;
class CGConstList {
Vector<Value> list;
explicit CGConstList(ExclusiveContext* cx) : list(cx) {}
bool append(Value v) { MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom()); return list.append(v); }
size_t length() const { return list.length(); }
void finish(ConstArray* array);
struct CGObjectList {
uint32_t length; /* number of emitted so far objects */
ObjectBox* lastbox; /* last emitted object */
CGObjectList() : length(0), lastbox(nullptr) {}
unsigned add(ObjectBox* objbox);
unsigned indexOf(JSObject* obj);
void finish(ObjectArray* array);
ObjectBox* find(uint32_t index);
struct CGTryNoteList {
Vector<JSTryNote> list;
explicit CGTryNoteList(ExclusiveContext* cx) : list(cx) {}
bool append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end);
size_t length() const { return list.length(); }
void finish(TryNoteArray* array);
struct CGBlockScopeNote : public BlockScopeNote
// The end offset. Used to compute the length; may need adjusting first if
// in the prologue.
uint32_t end;
// Is the start offset in the prologue?
bool startInPrologue;
// Is the end offset in the prologue?
bool endInPrologue;
struct CGBlockScopeList {
Vector<CGBlockScopeNote> list;
explicit CGBlockScopeList(ExclusiveContext* cx) : list(cx) {}
bool append(uint32_t scopeObject, uint32_t offset, bool inPrologue, uint32_t parent);
uint32_t findEnclosingScope(uint32_t index);
void recordEnd(uint32_t index, uint32_t offset, bool inPrologue);
size_t length() const { return list.length(); }
void finish(BlockScopeArray* array, uint32_t prologueLength);
struct CGYieldOffsetList {
Vector<uint32_t> list;
explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {}
bool append(uint32_t offset) { return list.append(offset); }
size_t length() const { return list.length(); }
void finish(YieldOffsetArray& array, uint32_t prologueLength);
struct LoopStmtInfo;
struct StmtInfoBCE;
// Use zero inline elements because these go on the stack and affect how many
// nested functions are possible.
typedef Vector<jsbytecode, 0> BytecodeVector;
typedef Vector<jssrcnote, 0> SrcNotesVector;
// This enum tells BytecodeEmitter::emitVariables and the destructuring
// methods how emit the given Parser::variables parse tree.
enum VarEmitOption {
// The normal case. Emit code to evaluate initializer expressions and
// assign them to local variables. Also emit JSOP_DEF{VAR,LET,CONST}
// opcodes in the prologue if the declaration occurs at toplevel.
// Emit only JSOP_DEFVAR opcodes, in the prologue, if necessary. This is
// used in one case: `for (var $BindingPattern in/of obj)`. If we're at
// toplevel, the variable(s) must be defined with JSOP_DEFVAR, but they're
// populated inside the loop, via emitAssignment.
// Emit code to evaluate initializer expressions and leave those values on
// the stack. This is used to implement `for (let/const ...;;)` and
// deprecated `let` blocks.
struct BytecodeEmitter
SharedContext* const sc; /* context shared between parsing and bytecode generation */
ExclusiveContext* const cx;
BytecodeEmitter* const parent; /* enclosing function or global context */
Rooted<JSScript*> script; /* the JSScript we're ultimately producing */
Rooted<LazyScript*> lazyScript; /* the lazy script if mode is LazyFunction,
nullptr otherwise. */
struct EmitSection {
BytecodeVector code; /* bytecode */
SrcNotesVector notes; /* source notes, see below */
ptrdiff_t lastNoteOffset; /* code offset for last source note */
uint32_t currentLine; /* line number for tree-based srcnote gen */
uint32_t lastColumn; /* zero-based column index on currentLine of
last SRC_COLSPAN-annotated opcode */
EmitSection(ExclusiveContext* cx, uint32_t lineNum)
: code(cx), notes(cx), lastNoteOffset(0), currentLine(lineNum), lastColumn(0)
EmitSection prologue, main, *current;
/* the parser */
Parser<FullParseHandler>* const parser;
HandleScript evalCaller; /* scripted caller info for eval and dbgapi */
StmtInfoStack<StmtInfoBCE> stmtStack;
OwnedAtomIndexMapPtr atomIndices; /* literals indexed for mapping */
unsigned firstLine; /* first line, for JSScript::initFromEmitter */
* Only unaliased locals have stack slots assigned to them. This vector is
* used to map a local index (which includes unaliased and aliased locals)
* to its stack slot index.
Vector<uint32_t, 16> localsToFrameSlots_;
int32_t stackDepth; /* current stack depth in script frame */
uint32_t maxStackDepth; /* maximum stack depth so far */
uint32_t arrayCompDepth; /* stack depth of array in comprehension */
unsigned emitLevel; /* emitTree recursion level */
CGConstList constList; /* constants to be included with the script */
CGObjectList objectList; /* list of emitted objects */
CGObjectList regexpList; /* list of emitted regexp that will be
cloned during execution */
CGTryNoteList tryNoteList; /* list of emitted try notes */
CGBlockScopeList blockScopeList;/* list of emitted block scope notes */
* For each yield op, map the yield index (stored as bytecode operand) to
* the offset of the next op.
CGYieldOffsetList yieldOffsetList;
uint16_t typesetCount; /* Number of JOF_TYPESET opcodes generated */
bool hasSingletons:1; /* script contains singleton initializer JSOP_OBJECT */
bool hasTryFinally:1; /* script contains finally block */
bool emittingForInit:1; /* true while emitting init expr of for; exclude 'in' */
bool emittingRunOnceLambda:1; /* true while emitting a lambda which is only
expected to run once. */
bool isRunOnceLambda();
bool insideEval:1; /* True if compiling an eval-expression or a function
nested inside an eval. */
const bool insideNonGlobalEval:1; /* True if this is a direct eval
call in some non-global scope. */
bool insideModule:1; /* True if compiling inside a module. */
enum EmitterMode {
* Emit JSOP_GETINTRINSIC instead of JSOP_GETNAME and assert that
* JSOP_GETNAME and JSOP_*GNAME don't ever get emitted. See the comment
* for the field |selfHostingMode| in Parser.h for details.
* Check the static scope chain of the root function for resolving free
* variable accesses in the script.
const EmitterMode emitterMode;
* Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
* space above their tempMark points. This means that you cannot alloc from
* tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
* destruction.
BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
bool insideEval, HandleScript evalCaller,
bool insideNonGlobalEval, uint32_t lineNum, EmitterMode emitterMode = Normal);
bool init();
bool updateLocalsToFrameSlots();
StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); }
StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
JSObject* innermostStaticScope() const;
JSObject* blockScopeOfDef(Definition* dn) const {
return parser->blockScopes[dn->pn_blockid];
bool atBodyLevel() const;
uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op);
bool makeAtomIndex(JSAtom* atom, jsatomid* indexp) {
AtomIndexAddPtr p = atomIndices->lookupForAdd(atom);
if (p) {
*indexp = p.value();
return true;
jsatomid index = atomIndices->count();
if (!atomIndices->add(p, atom, index))
return false;
*indexp = index;
return true;
bool isInLoop();
bool checkSingletonContext();
// Check whether our function is in a run-once context (a toplevel
// run-one script or a run-once lambda).
bool checkRunOnceContext();
bool needsImplicitThis();
void tellDebuggerAboutCompiledScript(ExclusiveContext* cx);
inline TokenStream* tokenStream();
BytecodeVector& code() const { return current->code; }
jsbytecode* code(ptrdiff_t offset) const { return current->code.begin() + offset; }
ptrdiff_t offset() const { return current->code.end() - current->code.begin(); }
ptrdiff_t prologueOffset() const { return prologue.code.end() - prologue.code.begin(); }
void switchToMain() { current = &main; }
void switchToPrologue() { current = &prologue; }
bool inPrologue() const { return current == &prologue; }
SrcNotesVector& notes() const { return current->notes; }
ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; }
unsigned currentLine() const { return current->currentLine; }
unsigned lastColumn() const { return current->lastColumn; }
bool reportError(ParseNode* pn, unsigned errorNumber, ...);
bool reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...);
bool reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...);
// If pn contains a useful expression, return true with *answer set to true.
// If pn contains a useless expression, return true with *answer set to
// false. Return false on error.
// The caller should initialize *answer to false and invoke this function on
// an expression statement or similar subtree to decide whether the tree
// could produce code that has any side effects. For an expression
// statement, we define useless code as code with no side effects, because
// the main effect, the value left on the stack after the code executes,
// will be discarded by a pop bytecode.
bool checkSideEffects(ParseNode* pn, bool* answer);
#ifdef DEBUG
bool checkStrictOrSloppy(JSOp op);
// Append a new source note of the given type (and therefore size) to the
// notes dynamic array, updating noteCount. Return the new note's index
// within the array pointed at by current->notes as outparam.
bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr);
bool newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp = nullptr);
bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2,
unsigned* indexp = nullptr);
void copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes);
bool setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset);
// NB: this function can add at most one extra extended delta note.
bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta);
// Finish taking source notes in cx's notePool. If successful, the final
// source note count is stored in the out outparam.
bool finishTakingSrcNotes(uint32_t* out);
void setJumpOffsetAt(ptrdiff_t off);
// Control whether emitTree emits a line number note.
enum EmitLineNumberNote {
// Emit code for the tree rooted at pn.
bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
// Emit function code for the tree rooted at body.
bool emitFunctionScript(ParseNode* body);
// Emit module code for the tree rooted at body.
bool emitModuleScript(ParseNode* body);
// If op is JOF_TYPESET (see the type barriers comment in TypeInference.h),
// reserve a type set to store its result.
void checkTypeSet(JSOp op);
void updateDepth(ptrdiff_t target);
bool updateLineNumberNotes(uint32_t offset);
bool updateSourceCoordNotes(uint32_t offset);
bool bindNameToSlot(ParseNode* pn);
bool bindNameToSlotHelper(ParseNode* pn);
void strictifySetNameNode(ParseNode* pn);
JSOp strictifySetNameOp(JSOp op);
bool tryConvertFreeName(ParseNode* pn);
void popStatement();
void pushStatement(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top);
void pushStatementInner(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top);
void pushLoopStatement(LoopStmtInfo* stmt, StmtType type, ptrdiff_t top);
bool enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType stmtType);
bool leaveNestedScope(StmtInfoBCE* stmt);
bool enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp,
unsigned alreadyPushed = 0);
bool computeAliasedSlots(Handle<StaticBlockObject*> blockObj);
bool lookupAliasedName(HandleScript script, PropertyName* name, uint32_t* pslot,
ParseNode* pn = nullptr);
bool lookupAliasedNameSlot(PropertyName* name, ScopeCoordinate* sc);
// In a function, block-scoped locals go after the vars, and form part of the
// fixed part of a stack frame. Outside a function, there are no fixed vars,
// but block-scoped locals still form part of the fixed part of a stack frame
// and are thus addressable via GETLOCAL and friends.
void computeLocalOffset(Handle<StaticBlockObject*> blockObj);
bool flushPops(int* npops);
bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset);
// Emit one bytecode.
bool emit1(JSOp op);
// Emit two bytecodes, an opcode (op) with a byte of immediate operand
// (op1).
bool emit2(JSOp op, uint8_t op1);
// Emit three bytecodes, an opcode with two bytes of immediate operands.
bool emit3(JSOp op, jsbytecode op1, jsbytecode op2);
// Helper to emit JSOP_DUPAT. The argument is the value's depth on the
// JS stack, as measured from the top.
bool emitDupAt(unsigned slotFromTop);
// Emit a bytecode followed by an uint16 immediate operand stored in
// big-endian order.
bool emitUint16Operand(JSOp op, uint32_t operand);
// Emit a bytecode followed by an uint32 immediate operand.
bool emitUint32Operand(JSOp op, uint32_t operand);
// Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr);
bool emitNumberOp(double dval);
bool emitThisLiteral(ParseNode* pn);
bool emitCreateFunctionThis();
bool emitGetFunctionThis(ParseNode* pn);
bool emitGetThisForSuperBase(ParseNode* pn);
bool emitSetThis(ParseNode* pn);
// These functions are used to emit GETLOCAL/GETALIASEDVAR or
// SETLOCAL/SETALIASEDVAR for a particular binding. The CallObject must be
// on top of the scope chain.
bool emitLoadFromTopScope(BindingIter& bi);
bool emitStoreToTopScope(BindingIter& bi);
bool emitJump(JSOp op, ptrdiff_t off, ptrdiff_t* jumpOffset = nullptr);
bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr);
bool emitLoopHead(ParseNode* nextpn);
bool emitLoopEntry(ParseNode* nextpn);
// Emit a backpatch op with offset pointing to the previous jump of this
// type, so that we can walk back up the chain fixing up the op and jump
// offset.
bool emitBackPatchOp(ptrdiff_t* lastp);
void backPatch(ptrdiff_t last, jsbytecode* target, jsbytecode op);
bool emitGoto(StmtInfoBCE* toStmt, ptrdiff_t* lastp, SrcNoteType noteType = SRC_NULL);
bool emitIndex32(JSOp op, uint32_t index);
bool emitIndexOp(JSOp op, uint32_t index);
bool emitAtomOp(JSAtom* atom, JSOp op);
bool emitAtomOp(ParseNode* pn, JSOp op);
bool emitArrayLiteral(ParseNode* pn);
bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
bool emitArrayComp(ParseNode* pn);
bool emitInternedObjectOp(uint32_t index, JSOp op);
bool emitObjectOp(ObjectBox* objbox, JSOp op);
bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
bool emitRegExp(uint32_t index);
MOZ_NEVER_INLINE bool emitFunction(ParseNode* pn, bool needsProto = false);
MOZ_NEVER_INLINE bool emitObject(ParseNode* pn);
bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type);
// To catch accidental misuse, emitUint16Operand/emit3 assert that they are
// not used to unconditionally emit JSOP_GETLOCAL. Variable access should
// instead be emitted using EmitVarOp. In special cases, when the caller
// definitely knows that a given local slot is unaliased, this function may be
// used as a non-asserting version of emitUint16Operand.
bool emitLocalOp(JSOp op, uint32_t slot);
bool emitScopeCoordOp(JSOp op, ScopeCoordinate sc);
bool emitAliasedVarOp(JSOp op, ParseNode* pn);
bool emitAliasedVarOp(JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical);
bool emitUnaliasedVarOp(JSOp op, uint32_t slot, MaybeCheckLexical checkLexical);
bool emitVarOp(ParseNode* pn, JSOp op);
bool emitVarIncDec(ParseNode* pn);
bool emitNameOp(ParseNode* pn, bool callContext);
bool emitNameIncDec(ParseNode* pn);
bool maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result);
bool emitVariables(ParseNode* pn, VarEmitOption emitOption);
bool emitSingleVariable(ParseNode* pn, ParseNode* binding, ParseNode* initializer,
VarEmitOption emitOption);
bool emitNewInit(JSProtoKey key);
bool emitSingletonInitialiser(ParseNode* pn);
bool emitPrepareIteratorResult();
bool emitFinishIteratorResult(bool done);
bool iteratorResultShape(unsigned* shape);
bool emitYield(ParseNode* pn);
bool emitYieldOp(JSOp op);
bool emitYieldStar(ParseNode* iter, ParseNode* gen);
bool emitPropLHS(ParseNode* pn);
bool emitPropOp(ParseNode* pn, JSOp op);
bool emitPropIncDec(ParseNode* pn);
bool emitComputedPropertyName(ParseNode* computedPropName);
// Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
// opcode onto the stack in the right order. In the case of SETELEM, the
// value to be assigned must already be pushed.
enum class EmitElemOption { Get, Set, Call, IncDec };
bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
bool emitElemOpBase(JSOp op);
bool emitElemOp(ParseNode* pn, JSOp op);
bool emitElemIncDec(ParseNode* pn);
bool emitCatch(ParseNode* pn);
bool emitIf(ParseNode* pn);
bool emitWith(ParseNode* pn);
MOZ_NEVER_INLINE bool emitLabeledStatement(const LabeledStatement* pn);
MOZ_NEVER_INLINE bool emitLetBlock(ParseNode* pnLet);
MOZ_NEVER_INLINE bool emitLexicalScope(ParseNode* pn);
MOZ_NEVER_INLINE bool emitSwitch(ParseNode* pn);
MOZ_NEVER_INLINE bool emitTry(ParseNode* pn);
// EmitDestructuringLHS assumes the to-be-destructured value has been pushed on
// the stack and emits code to destructure a single lhs expression (either a
// name or a compound []/{} expression).
// If emitOption is InitializeVars, the to-be-destructured value is assigned to
// locals and ultimately the initial slot is popped (-1 total depth change).
// If emitOption is PushInitialValues, the to-be-destructured value is replaced
// with the initial values of the N (where 0 <= N) variables assigned in the
// lhs expression. (Same post-condition as EmitDestructuringOpsHelper)
bool emitDestructuringLHS(ParseNode* target, VarEmitOption emitOption);
bool emitDestructuringOps(ParseNode* pattern, bool isLet = false);
bool emitDestructuringOpsHelper(ParseNode* pattern, VarEmitOption emitOption);
bool emitDestructuringOpsArrayHelper(ParseNode* pattern, VarEmitOption emitOption);
bool emitDestructuringOpsObjectHelper(ParseNode* pattern, VarEmitOption emitOption);
typedef bool
(*DestructuringDeclEmitter)(BytecodeEmitter* bce, JSOp prologueOp, ParseNode* pn);
template <DestructuringDeclEmitter EmitName>
bool emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* pattern);
bool emitDestructuringDecls(JSOp prologueOp, ParseNode* pattern);
// Emit code to initialize all destructured names to the value on the top of
// the stack.
bool emitInitializeDestructuringDecls(JSOp prologueOp, ParseNode* pattern);
// Throw a TypeError if the value atop the stack isn't convertible to an
// object, with no overall effect on the stack.
bool emitRequireObjectCoercible();
// emitIterator expects the iterable to already be on the stack.
// It will replace that stack value with the corresponding iterator
bool emitIterator();
// Pops iterator from the top of the stack. Pushes the result of |.next()|
// onto the stack.
bool emitIteratorNext(ParseNode* pn);
// Check if the value on top of the stack is "undefined". If so, replace
// that value on the stack with the value defined by |defaultExpr|.
bool emitDefault(ParseNode* defaultExpr);
bool emitCallSiteObject(ParseNode* pn);
bool emitTemplateString(ParseNode* pn);
bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs);
bool emitReturn(ParseNode* pn);
bool emitStatement(ParseNode* pn);
bool emitStatementList(ParseNode* pn);
bool emitDeleteName(ParseNode* pn);
bool emitDeleteProperty(ParseNode* pn);
bool emitDeleteElement(ParseNode* pn);
bool emitDeleteExpression(ParseNode* pn);
bool emitTypeof(ParseNode* node, JSOp op);
bool emitUnary(ParseNode* pn);
bool emitRightAssociative(ParseNode* pn);
bool emitLeftAssociative(ParseNode* pn);
bool emitLogical(ParseNode* pn);
bool emitSequenceExpr(ParseNode* pn);
MOZ_NEVER_INLINE bool emitIncOrDec(ParseNode* pn);
bool emitConditionalExpression(ConditionalExpression& conditional);
bool emitCallOrNew(ParseNode* pn);
bool emitSelfHostedCallFunction(ParseNode* pn);
bool emitSelfHostedResumeGenerator(ParseNode* pn);
bool emitSelfHostedForceInterpreter(ParseNode* pn);
bool emitComprehensionFor(ParseNode* compFor);
bool emitComprehensionForIn(ParseNode* pn);
bool emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letBlockScope);
bool emitComprehensionForOf(ParseNode* pn);
bool emitDo(ParseNode* pn);
bool emitFor(ParseNode* pn);
bool emitForIn(ParseNode* pn);
bool emitForInOrOfVariables(ParseNode* pn);
bool emitCStyleFor(ParseNode* pn);
bool emitWhile(ParseNode* pn);
bool emitBreak(PropertyName* label);
bool emitContinue(PropertyName* label);
bool emitArgsBody(ParseNode* pn);
bool emitDefaultsAndDestructuring(ParseNode* pn);
bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp);
bool pushInitialConstants(JSOp op, unsigned n);
bool initializeBlockScopedLocalsFromStack(Handle<StaticBlockObject*> blockObj);
// emitSpread expects the current index (I) of the array, the array itself
// and the iterator to be on the stack in that order (iterator on the bottom).
// It will pop the iterator and I, then iterate over the iterator by calling
// |.next()| and put the results into the I-th element of array with
// incrementing I, then push the result I (it will be original I +
// iteration count). The stack after iteration will look like |ARRAY INDEX|.
bool emitSpread();
// If type is StmtType::FOR_OF_LOOP, emit bytecode for a for-of loop.
// pn should be PNK_FOR, and pn->pn_left should be PNK_FOROF.
// If type is StmtType::SPREAD, emit bytecode for spread operator.
// pn should be nullptr.
// Please refer the comment above emitSpread for additional information about
// stack convention.
bool emitForOf(StmtType type, ParseNode* pn);
bool emitClass(ParseNode* pn);
bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false);
bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false);
bool emitSuperElemOperands(ParseNode* pn, EmitElemOption opts = EmitElemOption::Get);
bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false);
} /* namespace frontend */
} /* namespace js */
#endif /* frontend_BytecodeEmitter_h */