| /* -*- 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 http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * JS bytecode generation. |
| */ |
| |
| #include "frontend/BytecodeEmitter.h" |
| |
| #include "mozilla/ArrayUtils.h" |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/FloatingPoint.h" |
| #include "mozilla/Maybe.h" |
| #include "mozilla/PodOperations.h" |
| #include "mozilla/UniquePtr.h" |
| |
| #include <string.h> |
| |
| #include "jsapi.h" |
| #include "jsatom.h" |
| #include "jscntxt.h" |
| #include "jsfun.h" |
| #include "jsnum.h" |
| #include "jsopcode.h" |
| #include "jsscript.h" |
| #include "jstypes.h" |
| #include "jsutil.h" |
| |
| #include "asmjs/AsmJSLink.h" |
| #include "frontend/Parser.h" |
| #include "frontend/TokenStream.h" |
| #include "vm/Debugger.h" |
| #include "vm/GeneratorObject.h" |
| #include "vm/Stack.h" |
| |
| #include "jsatominlines.h" |
| #include "jsobjinlines.h" |
| #include "jsscriptinlines.h" |
| |
| #include "frontend/ParseMaps-inl.h" |
| #include "frontend/ParseNode-inl.h" |
| #include "vm/NativeObject-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| |
| using namespace js; |
| using namespace js::gc; |
| using namespace js::frontend; |
| |
| using mozilla::Maybe; |
| using mozilla::Some; |
| using mozilla::DebugOnly; |
| using mozilla::NumberIsInt32; |
| using mozilla::PodCopy; |
| using mozilla::UniquePtr; |
| |
| struct frontend::StmtInfoBCE : public StmtInfoBase |
| { |
| StmtInfoBCE* enclosing; |
| StmtInfoBCE* enclosingScope; |
| |
| ptrdiff_t update; /* loop update offset (top if none) */ |
| ptrdiff_t breaks; /* offset of last break in loop */ |
| ptrdiff_t continues; /* offset of last continue in loop */ |
| uint32_t blockScopeIndex; /* index of scope in BlockScopeArray */ |
| |
| explicit StmtInfoBCE(ExclusiveContext* cx) : StmtInfoBase(cx) {} |
| |
| void setTop(ptrdiff_t top) { |
| update = top; |
| breaks = -1; |
| continues = -1; |
| } |
| |
| /* |
| * To reuse space, alias two of the ptrdiff_t fields for use during |
| * try/catch/finally code generation and backpatching. |
| * |
| * Only a loop, switch, or label statement info record can have breaks and |
| * continues, and only a for loop has an update backpatch chain, so it's |
| * safe to overlay these for the "trying" StmtTypes. |
| */ |
| |
| ptrdiff_t& gosubs() { |
| MOZ_ASSERT(type == StmtType::FINALLY); |
| return breaks; |
| } |
| |
| ptrdiff_t& guardJump() { |
| MOZ_ASSERT(type == StmtType::TRY || type == StmtType::FINALLY); |
| return continues; |
| } |
| }; |
| |
| struct frontend::LoopStmtInfo : public StmtInfoBCE |
| { |
| int32_t stackDepth; // Stack depth when this loop was pushed. |
| uint32_t loopDepth; // Loop depth. |
| |
| // Can we OSR into Ion from here? True unless there is non-loop state on the stack. |
| bool canIonOsr; |
| |
| explicit LoopStmtInfo(ExclusiveContext* cx) : StmtInfoBCE(cx) {} |
| |
| static LoopStmtInfo* fromStmtInfo(StmtInfoBCE* stmt) { |
| MOZ_ASSERT(stmt->isLoop()); |
| return static_cast<LoopStmtInfo*>(stmt); |
| } |
| }; |
| |
| BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, |
| Parser<FullParseHandler>* parser, SharedContext* sc, |
| HandleScript script, Handle<LazyScript*> lazyScript, |
| bool insideEval, HandleScript evalCaller, |
| bool insideNonGlobalEval, uint32_t lineNum, |
| EmitterMode emitterMode) |
| : sc(sc), |
| cx(sc->context), |
| parent(parent), |
| script(cx, script), |
| lazyScript(cx, lazyScript), |
| prologue(cx, lineNum), |
| main(cx, lineNum), |
| current(&main), |
| parser(parser), |
| evalCaller(evalCaller), |
| stmtStack(cx), |
| atomIndices(cx), |
| firstLine(lineNum), |
| localsToFrameSlots_(cx), |
| stackDepth(0), maxStackDepth(0), |
| arrayCompDepth(0), |
| emitLevel(0), |
| constList(cx), |
| tryNoteList(cx), |
| blockScopeList(cx), |
| yieldOffsetList(cx), |
| typesetCount(0), |
| hasSingletons(false), |
| hasTryFinally(false), |
| emittingForInit(false), |
| emittingRunOnceLambda(false), |
| insideEval(insideEval), |
| insideNonGlobalEval(insideNonGlobalEval), |
| insideModule(false), |
| emitterMode(emitterMode) |
| { |
| MOZ_ASSERT_IF(evalCaller, insideEval); |
| MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); |
| } |
| |
| bool |
| BytecodeEmitter::init() |
| { |
| return atomIndices.ensureMap(cx); |
| } |
| |
| bool |
| BytecodeEmitter::updateLocalsToFrameSlots() |
| { |
| // Assign stack slots to unaliased locals (aliased locals are stored in the |
| // call object and don't need their own stack slots). We do this by filling |
| // a Vector that can be used to map a local to its stack slot. |
| |
| if (localsToFrameSlots_.length() == script->bindings.numLocals()) { |
| // CompileScript calls updateNumBlockScoped to update the block scope |
| // depth. Do nothing if the depth didn't change. |
| return true; |
| } |
| |
| localsToFrameSlots_.clear(); |
| |
| if (!localsToFrameSlots_.reserve(script->bindings.numLocals())) |
| return false; |
| |
| uint32_t slot = 0; |
| for (BindingIter bi(script); !bi.done(); bi++) { |
| if (bi->kind() == Binding::ARGUMENT) |
| continue; |
| |
| if (bi->aliased()) |
| localsToFrameSlots_.infallibleAppend(UINT32_MAX); |
| else |
| localsToFrameSlots_.infallibleAppend(slot++); |
| } |
| |
| for (size_t i = 0; i < script->bindings.numBlockScoped(); i++) |
| localsToFrameSlots_.infallibleAppend(slot++); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset) |
| { |
| *offset = code().length(); |
| |
| // Start it off moderately large to avoid repeated resizings early on. |
| // ~98% of cases fit within 1024 bytes. |
| if (code().capacity() == 0 && !code().reserve(1024)) |
| return false; |
| |
| if (!code().growBy(delta)) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| return true; |
| } |
| |
| void |
| BytecodeEmitter::updateDepth(ptrdiff_t target) |
| { |
| jsbytecode* pc = code(target); |
| |
| int nuses = StackUses(nullptr, pc); |
| int ndefs = StackDefs(nullptr, pc); |
| |
| stackDepth -= nuses; |
| MOZ_ASSERT(stackDepth >= 0); |
| stackDepth += ndefs; |
| if ((uint32_t)stackDepth > maxStackDepth) |
| maxStackDepth = stackDepth; |
| } |
| |
| #ifdef DEBUG |
| bool |
| BytecodeEmitter::checkStrictOrSloppy(JSOp op) |
| { |
| if (IsCheckStrictOp(op) && !sc->strict()) |
| return false; |
| if (IsCheckSloppyOp(op) && sc->strict()) |
| return false; |
| return true; |
| } |
| #endif |
| |
| bool |
| BytecodeEmitter::emit1(JSOp op) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| |
| ptrdiff_t offset; |
| if (!emitCheck(1, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| updateDepth(offset); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emit2(JSOp op, uint8_t op1) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| |
| ptrdiff_t offset; |
| if (!emitCheck(2, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| code[1] = jsbytecode(op1); |
| updateDepth(offset); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| |
| /* These should filter through emitVarOp. */ |
| MOZ_ASSERT(!IsArgOp(op)); |
| MOZ_ASSERT(!IsLocalOp(op)); |
| |
| ptrdiff_t offset; |
| if (!emitCheck(3, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| code[1] = op1; |
| code[2] = op2; |
| updateDepth(offset); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitN(JSOp op, size_t extra, ptrdiff_t* offset) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| ptrdiff_t length = 1 + ptrdiff_t(extra); |
| |
| ptrdiff_t off; |
| if (!emitCheck(length, &off)) |
| return false; |
| |
| jsbytecode* code = this->code(off); |
| code[0] = jsbytecode(op); |
| /* The remaining |extra| bytes are set by the caller */ |
| |
| /* |
| * Don't updateDepth if op's use-count comes from the immediate |
| * operand yet to be stored in the extra bytes after op. |
| */ |
| if (CodeSpec[op].nuses >= 0) |
| updateDepth(off); |
| |
| if (offset) |
| *offset = off; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitJump(JSOp op, ptrdiff_t off, ptrdiff_t* jumpOffset) |
| { |
| ptrdiff_t offset; |
| if (!emitCheck(5, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| SET_JUMP_OFFSET(code, off); |
| updateDepth(offset); |
| if (jumpOffset) |
| *jumpOffset = offset; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) |
| { |
| if (pn && !updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| return emit3(op, ARGC_HI(argc), ARGC_LO(argc)); |
| } |
| |
| bool |
| BytecodeEmitter::emitDupAt(unsigned slotFromTop) |
| { |
| MOZ_ASSERT(slotFromTop < unsigned(stackDepth)); |
| |
| if (slotFromTop >= JS_BIT(24)) { |
| reportError(nullptr, JSMSG_TOO_MANY_LOCALS); |
| return false; |
| } |
| |
| ptrdiff_t off; |
| if (!emitN(JSOP_DUPAT, 3, &off)) |
| return false; |
| |
| jsbytecode* pc = code(off); |
| SET_UINT24(pc, slotFromTop); |
| return true; |
| } |
| |
| /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ |
| const char js_with_statement_str[] = "with statement"; |
| const char js_finally_block_str[] = "finally block"; |
| |
| static const char * const statementName[] = { |
| "label statement", /* LABEL */ |
| "if statement", /* IF */ |
| "else statement", /* ELSE */ |
| "destructuring body", /* BODY */ |
| "switch statement", /* SWITCH */ |
| "block", /* BLOCK */ |
| js_with_statement_str, /* WITH */ |
| "catch block", /* CATCH */ |
| "try block", /* TRY */ |
| js_finally_block_str, /* FINALLY */ |
| js_finally_block_str, /* SUBROUTINE */ |
| "do loop", /* DO_LOOP */ |
| "for loop", /* FOR_LOOP */ |
| "for/in loop", /* FOR_IN_LOOP */ |
| "for/of loop", /* FOR_OF_LOOP */ |
| "while loop", /* WHILE_LOOP */ |
| "spread", /* SPREAD */ |
| }; |
| |
| static_assert(MOZ_ARRAY_LENGTH(statementName) == uint16_t(StmtType::LIMIT), |
| "statementName array and StmtType enum must be consistent"); |
| |
| static const char* |
| StatementName(StmtInfoBCE* stmt) |
| { |
| if (!stmt) |
| return js_script_str; |
| return statementName[uint16_t(stmt->type)]; |
| } |
| |
| static void |
| ReportStatementTooLarge(TokenStream& ts, StmtInfoBCE* stmt) |
| { |
| ts.reportError(JSMSG_NEED_DIET, StatementName(stmt)); |
| } |
| |
| /* |
| * 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 |
| BytecodeEmitter::emitBackPatchOp(ptrdiff_t* lastp) |
| { |
| ptrdiff_t delta = offset() - *lastp; |
| *lastp = offset(); |
| MOZ_ASSERT(delta > 0); |
| return emitJump(JSOP_BACKPATCH, delta); |
| } |
| |
| static inline unsigned |
| LengthOfSetLine(unsigned line) |
| { |
| return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); |
| } |
| |
| /* Updates line number notes, not column notes. */ |
| bool |
| BytecodeEmitter::updateLineNumberNotes(uint32_t offset) |
| { |
| TokenStream* ts = &parser->tokenStream; |
| bool onThisLine; |
| if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine)) |
| return ts->reportError(JSMSG_OUT_OF_MEMORY); |
| if (!onThisLine) { |
| unsigned line = ts->srcCoords.lineNum(offset); |
| unsigned delta = line - currentLine(); |
| |
| /* |
| * Encode any change in the current source line number by using |
| * either several SRC_NEWLINE notes or just one SRC_SETLINE note, |
| * whichever consumes less space. |
| * |
| * NB: We handle backward line number deltas (possible with for |
| * loops where the update part is emitted after the body, but its |
| * line number is <= any line number in the body) here by letting |
| * unsigned delta_ wrap to a very large number, which triggers a |
| * SRC_SETLINE. |
| */ |
| current->currentLine = line; |
| current->lastColumn = 0; |
| if (delta >= LengthOfSetLine(line)) { |
| if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line))) |
| return false; |
| } else { |
| do { |
| if (!newSrcNote(SRC_NEWLINE)) |
| return false; |
| } while (--delta != 0); |
| } |
| } |
| return true; |
| } |
| |
| /* Updates the line number and column number information in the source notes. */ |
| bool |
| BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) |
| { |
| if (!updateLineNumberNotes(offset)) |
| return false; |
| |
| uint32_t columnIndex = parser->tokenStream.srcCoords.columnIndex(offset); |
| ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn); |
| if (colspan != 0) { |
| // If the column span is so large that we can't store it, then just |
| // discard this information. This can happen with minimized or otherwise |
| // machine-generated code. Even gigantic column numbers are still |
| // valuable if you have a source map to relate them to something real; |
| // but it's better to fail soft here. |
| if (!SN_REPRESENTABLE_COLSPAN(colspan)) |
| return true; |
| if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) |
| return false; |
| current->lastColumn = columnIndex; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitLoopHead(ParseNode* nextpn) |
| { |
| if (nextpn) { |
| /* |
| * Try to give the JSOP_LOOPHEAD the same line number as the next |
| * instruction. nextpn is often a block, in which case the next |
| * instruction typically comes from the first statement inside. |
| */ |
| MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); |
| if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) |
| nextpn = nextpn->pn_head; |
| if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) |
| return false; |
| } |
| |
| return emit1(JSOP_LOOPHEAD); |
| } |
| |
| bool |
| BytecodeEmitter::emitLoopEntry(ParseNode* nextpn) |
| { |
| if (nextpn) { |
| /* Update the line number, as for LOOPHEAD. */ |
| MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); |
| if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) |
| nextpn = nextpn->pn_head; |
| if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) |
| return false; |
| } |
| |
| LoopStmtInfo* loop = LoopStmtInfo::fromStmtInfo(innermostStmt()); |
| MOZ_ASSERT(loop->loopDepth > 0); |
| |
| uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loop->loopDepth, loop->canIonOsr); |
| return emit2(JSOP_LOOPENTRY, loopDepthAndFlags); |
| } |
| |
| void |
| BytecodeEmitter::checkTypeSet(JSOp op) |
| { |
| if (CodeSpec[op].format & JOF_TYPESET) { |
| if (typesetCount < UINT16_MAX) |
| typesetCount++; |
| } |
| } |
| |
| bool |
| BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) |
| { |
| MOZ_ASSERT(operand <= UINT16_MAX); |
| if (!emit3(op, UINT16_HI(operand), UINT16_LO(operand))) |
| return false; |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) |
| { |
| ptrdiff_t off; |
| if (!emitN(op, 4, &off)) |
| return false; |
| SET_UINT32(code(off), operand); |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::flushPops(int* npops) |
| { |
| MOZ_ASSERT(*npops != 0); |
| if (!emitUint16Operand(JSOP_POPN, *npops)) |
| return false; |
| |
| *npops = 0; |
| return true; |
| } |
| |
| namespace { |
| |
| class NonLocalExitScope { |
| BytecodeEmitter* bce; |
| const uint32_t savedScopeIndex; |
| const int savedDepth; |
| uint32_t openScopeIndex; |
| |
| NonLocalExitScope(const NonLocalExitScope&) = delete; |
| |
| public: |
| explicit NonLocalExitScope(BytecodeEmitter* bce_) |
| : bce(bce_), |
| savedScopeIndex(bce->blockScopeList.length()), |
| savedDepth(bce->stackDepth), |
| openScopeIndex(UINT32_MAX) |
| { |
| if (StmtInfoBCE* stmt = bce->innermostScopeStmt()) |
| openScopeIndex = stmt->blockScopeIndex; |
| } |
| ~NonLocalExitScope() { |
| for (uint32_t n = savedScopeIndex; n < bce->blockScopeList.length(); n++) |
| bce->blockScopeList.recordEnd(n, bce->offset(), bce->inPrologue()); |
| bce->stackDepth = savedDepth; |
| } |
| |
| bool popScopeForNonLocalExit(uint32_t blockScopeIndex) { |
| uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex); |
| uint32_t parent = openScopeIndex; |
| |
| if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), bce->inPrologue(), parent)) |
| return false; |
| openScopeIndex = bce->blockScopeList.length() - 1; |
| return true; |
| } |
| |
| bool prepareForNonLocalJump(StmtInfoBCE* toStmt); |
| }; |
| |
| /* |
| * Emit additional bytecode(s) for non-local jumps. |
| */ |
| bool |
| NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE* toStmt) |
| { |
| int npops = 0; |
| |
| #define FLUSH_POPS() if (npops && !bce->flushPops(&npops)) return false |
| |
| for (StmtInfoBCE* stmt = bce->innermostStmt(); stmt != toStmt; stmt = stmt->enclosing) { |
| switch (stmt->type) { |
| case StmtType::FINALLY: |
| FLUSH_POPS(); |
| if (!bce->emitBackPatchOp(&stmt->gosubs())) |
| return false; |
| break; |
| |
| case StmtType::WITH: |
| if (!bce->emit1(JSOP_LEAVEWITH)) |
| return false; |
| MOZ_ASSERT(stmt->linksScope()); |
| if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) |
| return false; |
| break; |
| |
| case StmtType::FOR_OF_LOOP: |
| npops += 2; |
| break; |
| |
| case StmtType::FOR_IN_LOOP: |
| /* The iterator and the current value are on the stack. */ |
| npops += 1; |
| FLUSH_POPS(); |
| if (!bce->emit1(JSOP_ENDITER)) |
| return false; |
| break; |
| |
| case StmtType::SPREAD: |
| MOZ_ASSERT_UNREACHABLE("can't break/continue/return from inside a spread"); |
| break; |
| |
| case StmtType::SUBROUTINE: |
| /* |
| * There's a [exception or hole, retsub pc-index] pair and the |
| * possible return value on the stack that we need to pop. |
| */ |
| npops += 3; |
| break; |
| |
| default:; |
| } |
| |
| if (stmt->isBlockScope) { |
| StaticBlockObject& blockObj = stmt->staticBlock(); |
| if (blockObj.needsClone()) { |
| if (!bce->emit1(JSOP_POPBLOCKSCOPE)) |
| return false; |
| } else { |
| if (!bce->emit1(JSOP_DEBUGLEAVEBLOCK)) |
| return false; |
| } |
| if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) |
| return false; |
| } |
| } |
| |
| FLUSH_POPS(); |
| return true; |
| |
| #undef FLUSH_POPS |
| } |
| |
| } // anonymous namespace |
| |
| bool |
| BytecodeEmitter::emitGoto(StmtInfoBCE* toStmt, ptrdiff_t* lastp, SrcNoteType noteType) |
| { |
| NonLocalExitScope nle(this); |
| |
| if (!nle.prepareForNonLocalJump(toStmt)) |
| return false; |
| |
| if (noteType != SRC_NULL) { |
| if (!newSrcNote(noteType)) |
| return false; |
| } |
| |
| return emitBackPatchOp(lastp); |
| } |
| |
| void |
| BytecodeEmitter::backPatch(ptrdiff_t last, jsbytecode* target, jsbytecode op) |
| { |
| jsbytecode* pc = code(last); |
| jsbytecode* stop = code(-1); |
| while (pc != stop) { |
| ptrdiff_t delta = GET_JUMP_OFFSET(pc); |
| ptrdiff_t span = target - pc; |
| SET_JUMP_OFFSET(pc, span); |
| *pc = op; |
| pc -= delta; |
| } |
| } |
| |
| void |
| BytecodeEmitter::pushStatementInner(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top) |
| { |
| stmt->setTop(top); |
| stmtStack.push(stmt, type); |
| } |
| |
| void |
| BytecodeEmitter::pushStatement(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top) |
| { |
| pushStatementInner(stmt, type, top); |
| MOZ_ASSERT(!stmt->isLoop()); |
| } |
| |
| void |
| BytecodeEmitter::pushLoopStatement(LoopStmtInfo* stmt, StmtType type, ptrdiff_t top) |
| { |
| pushStatementInner(stmt, type, top); |
| MOZ_ASSERT(stmt->isLoop()); |
| |
| LoopStmtInfo* enclosingLoop = nullptr; |
| for (StmtInfoBCE* outer = stmt->enclosing; outer; outer = outer->enclosing) { |
| if (outer->isLoop()) { |
| enclosingLoop = LoopStmtInfo::fromStmtInfo(outer); |
| break; |
| } |
| } |
| |
| stmt->stackDepth = this->stackDepth; |
| stmt->loopDepth = enclosingLoop ? enclosingLoop->loopDepth + 1 : 1; |
| |
| int loopSlots; |
| if (type == StmtType::SPREAD) |
| loopSlots = 3; |
| else if (type == StmtType::FOR_IN_LOOP || type == StmtType::FOR_OF_LOOP) |
| loopSlots = 2; |
| else |
| loopSlots = 0; |
| |
| MOZ_ASSERT(loopSlots <= stmt->stackDepth); |
| |
| if (enclosingLoop) { |
| stmt->canIonOsr = (enclosingLoop->canIonOsr && |
| stmt->stackDepth == enclosingLoop->stackDepth + loopSlots); |
| } else { |
| stmt->canIonOsr = stmt->stackDepth == loopSlots; |
| } |
| } |
| |
| JSObject* |
| BytecodeEmitter::innermostStaticScope() const |
| { |
| if (StmtInfoBCE* stmt = innermostScopeStmt()) |
| return stmt->staticScope; |
| return sc->staticScope(); |
| } |
| |
| #ifdef DEBUG |
| static bool |
| AllLocalsAliased(StaticBlockObject& obj) |
| { |
| for (unsigned i = 0; i < obj.numVariables(); i++) |
| if (!obj.isAliased(i)) |
| return false; |
| return true; |
| } |
| #endif |
| |
| bool |
| BytecodeEmitter::computeAliasedSlots(Handle<StaticBlockObject*> blockObj) |
| { |
| uint32_t numAliased = script->bindings.numAliasedBodyLevelLocals(); |
| |
| for (unsigned i = 0; i < blockObj->numVariables(); i++) { |
| Definition* dn = blockObj->definitionParseNode(i); |
| |
| MOZ_ASSERT(dn->isDefn()); |
| |
| uint32_t index = dn->pn_scopecoord.slot(); |
| uint32_t slot; |
| |
| if (isAliasedName(this, dn)) { |
| slot = blockObj->blockIndexToSlot(index); |
| blockObj->setAliased(i, true); |
| } else { |
| // blockIndexToLocalIndex returns the frame slot following the |
| // unaliased locals. We add numAliased so that the slot value |
| // comes after all (aliased and unaliased) body level locals. |
| slot = numAliased + blockObj->blockIndexToLocalIndex(index); |
| blockObj->setAliased(i, false); |
| } |
| |
| if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot)) |
| return false; |
| |
| #ifdef DEBUG |
| for (ParseNode* pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { |
| MOZ_ASSERT(pnu->pn_lexdef == dn); |
| MOZ_ASSERT(!(pnu->pn_dflags & PND_BOUND)); |
| MOZ_ASSERT(pnu->pn_scopecoord.isFree()); |
| } |
| #endif |
| |
| } |
| |
| MOZ_ASSERT_IF(sc->allLocalsAliased(), AllLocalsAliased(*blockObj)); |
| |
| return true; |
| } |
| |
| void |
| BytecodeEmitter::computeLocalOffset(Handle<StaticBlockObject*> blockObj) |
| { |
| unsigned nbodyfixed = !sc->isGlobalContext() |
| ? script->bindings.numUnaliasedBodyLevelLocals() |
| : 0; |
| unsigned localOffset = nbodyfixed; |
| |
| if (StmtInfoBCE* stmt = innermostScopeStmt()) { |
| Rooted<NestedScopeObject*> outer(cx, stmt->staticScope); |
| for (; outer; outer = outer->enclosingNestedScope()) { |
| if (outer->is<StaticBlockObject>() && !IsStaticGlobalLexicalScope(outer)) { |
| StaticBlockObject& outerBlock = outer->as<StaticBlockObject>(); |
| localOffset = outerBlock.localOffset() + outerBlock.numVariables(); |
| break; |
| } |
| } |
| } |
| |
| MOZ_ASSERT(localOffset + blockObj->numVariables() |
| <= nbodyfixed + script->bindings.numBlockScoped()); |
| |
| blockObj->setLocalOffset(localOffset); |
| } |
| |
| // ~ Nested Scopes ~ |
| // |
| // A nested scope is a region of a compilation unit (function, script, or eval |
| // code) with an additional node on the scope chain. This node may either be a |
| // "with" object or a "block" object. "With" objects represent "with" scopes. |
| // Block objects represent lexical scopes, and contain named block-scoped |
| // bindings, for example "let" bindings or the exception in a catch block. |
| // Those variables may be local and thus accessible directly from the stack, or |
| // "aliased" (accessed by name from nested functions, or dynamically via nested |
| // "eval" or "with") and only accessible through the scope chain. |
| // |
| // All nested scopes are present on the "static scope chain". A nested scope |
| // that is a "with" scope will be present on the scope chain at run-time as |
| // well. A block scope may or may not have a corresponding link on the run-time |
| // scope chain; if no variable declared in the block scope is "aliased", then no |
| // scope chain node is allocated. |
| // |
| // To help debuggers, the bytecode emitter arranges to record the PC ranges |
| // comprehended by a nested scope, and ultimately attach them to the JSScript. |
| // An element in the "block scope array" specifies the PC range, and links to a |
| // NestedScopeObject in the object list of the script. That scope object is |
| // linked to the previous link in the static scope chain, if any. The static |
| // scope chain at any pre-retire PC can be retrieved using |
| // JSScript::getStaticScope(jsbytecode* pc). |
| // |
| // Block scopes store their locals in the fixed part of a stack frame, after the |
| // "fixed var" bindings. A fixed var binding is a "var" or legacy "const" |
| // binding that occurs in a function (as opposed to a script or in eval code). |
| // Only functions have fixed var bindings. |
| // |
| // To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a |
| // block scope, if the block has no aliased locals. This allows DebugScopes |
| // to invalidate any association between a debugger scope object, which can |
| // proxy access to unaliased stack locals, and the actual live frame. In |
| // normal, non-debug mode, this opcode does not cause any baseline code to be |
| // emitted. |
| // |
| // If the block has aliased locals, no DEBUGLEAVEBLOCK is emitted, and |
| // POPBLOCKSCOPE itself balances the debug scope mapping. This gets around a |
| // comedic situation where DEBUGLEAVEBLOCK may remove a block scope from the |
| // debug scope map, but the immediate following POPBLOCKSCOPE adds it back due |
| // to an onStep hook. |
| // |
| // Enter a nested scope with enterNestedScope. It will emit |
| // PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of |
| // the scope. Leave a nested scope with leaveNestedScope, which, for blocks, |
| // will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. (For "with" scopes it |
| // emits LEAVEWITH, of course.) Pass enterNestedScope a fresh StmtInfoBCE |
| // object, and pass that same object to the corresponding leaveNestedScope. If |
| // the statement is a block scope, pass StmtType::BLOCK as stmtType; otherwise for |
| // with scopes pass StmtType::WITH. |
| // |
| bool |
| BytecodeEmitter::enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType stmtType) |
| { |
| Rooted<NestedScopeObject*> scopeObj(cx, &objbox->object->as<NestedScopeObject>()); |
| uint32_t scopeObjectIndex = objectList.add(objbox); |
| |
| switch (stmtType) { |
| case StmtType::BLOCK: { |
| Rooted<StaticBlockObject*> blockObj(cx, &scopeObj->as<StaticBlockObject>()); |
| |
| computeLocalOffset(blockObj); |
| |
| if (!computeAliasedSlots(blockObj)) |
| return false; |
| |
| if (blockObj->needsClone()) { |
| if (!emitInternedObjectOp(scopeObjectIndex, JSOP_PUSHBLOCKSCOPE)) |
| return false; |
| } |
| |
| // Non-global block scopes are non-extensible. At this point the |
| // Parser has added all bindings to the StaticBlockObject, so we make |
| // it non-extensible. |
| if (!blockObj->makeNonExtensible(cx)) |
| return false; |
| break; |
| } |
| case StmtType::WITH: |
| MOZ_ASSERT(scopeObj->is<StaticWithObject>()); |
| if (!emitInternedObjectOp(scopeObjectIndex, JSOP_ENTERWITH)) |
| return false; |
| break; |
| default: |
| MOZ_CRASH("Unexpected scope statement"); |
| } |
| |
| uint32_t parent = BlockScopeNote::NoBlockScopeIndex; |
| if (StmtInfoBCE* stmt = innermostScopeStmt()) |
| parent = stmt->blockScopeIndex; |
| |
| stmt->blockScopeIndex = blockScopeList.length(); |
| if (!blockScopeList.append(scopeObjectIndex, offset(), inPrologue(), parent)) |
| return false; |
| |
| pushStatement(stmt, stmtType, offset()); |
| scopeObj->initEnclosingScope(innermostStaticScope()); |
| stmtStack.linkAsInnermostScopeStmt(stmt, *scopeObj); |
| MOZ_ASSERT(stmt->linksScope()); |
| stmt->isBlockScope = (stmtType == StmtType::BLOCK); |
| |
| return true; |
| } |
| |
| // Patches |breaks| and |continues| unless the top statement info record |
| // represents a try-catch-finally suite. |
| void |
| BytecodeEmitter::popStatement() |
| { |
| if (!innermostStmt()->isTrying()) { |
| backPatch(innermostStmt()->breaks, code().end(), JSOP_GOTO); |
| backPatch(innermostStmt()->continues, code(innermostStmt()->update), JSOP_GOTO); |
| } |
| |
| stmtStack.pop(); |
| } |
| |
| bool |
| BytecodeEmitter::leaveNestedScope(StmtInfoBCE* stmt) |
| { |
| MOZ_ASSERT(stmt == innermostScopeStmt()); |
| MOZ_ASSERT(stmt->isBlockScope == !(stmt->type == StmtType::WITH)); |
| uint32_t blockScopeIndex = stmt->blockScopeIndex; |
| |
| #ifdef DEBUG |
| MOZ_ASSERT(blockScopeList.list[blockScopeIndex].length == 0); |
| uint32_t blockObjIndex = blockScopeList.list[blockScopeIndex].index; |
| ObjectBox* blockObjBox = objectList.find(blockObjIndex); |
| NestedScopeObject* staticScope = &blockObjBox->object->as<NestedScopeObject>(); |
| MOZ_ASSERT(stmt->staticScope == staticScope); |
| MOZ_ASSERT_IF(!stmt->isBlockScope, staticScope->is<StaticWithObject>()); |
| #endif |
| |
| popStatement(); |
| |
| if (stmt->isBlockScope) { |
| if (stmt->staticScope->as<StaticBlockObject>().needsClone()) { |
| if (!emit1(JSOP_POPBLOCKSCOPE)) |
| return false; |
| } else { |
| if (!emit1(JSOP_DEBUGLEAVEBLOCK)) |
| return false; |
| } |
| } else { |
| if (!emit1(JSOP_LEAVEWITH)) |
| return false; |
| } |
| |
| blockScopeList.recordEnd(blockScopeIndex, offset(), inPrologue()); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitIndex32(JSOp op, uint32_t index) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| |
| const size_t len = 1 + UINT32_INDEX_LEN; |
| MOZ_ASSERT(len == size_t(CodeSpec[op].length)); |
| |
| ptrdiff_t offset; |
| if (!emitCheck(len, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| SET_UINT32_INDEX(code, index); |
| updateDepth(offset); |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) |
| { |
| MOZ_ASSERT(checkStrictOrSloppy(op)); |
| |
| const size_t len = CodeSpec[op].length; |
| MOZ_ASSERT(len >= 1 + UINT32_INDEX_LEN); |
| |
| ptrdiff_t offset; |
| if (!emitCheck(len, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = jsbytecode(op); |
| SET_UINT32_INDEX(code, index); |
| updateDepth(offset); |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) |
| { |
| MOZ_ASSERT(atom); |
| MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
| |
| // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of |
| // JSOP_GETNAME etc, to bypass |with| objects on the scope chain. |
| // It's safe to emit .this lookups though because |with| objects skip |
| // those. |
| MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME, |
| !sc->isDotVariable(atom) || atom == cx->names().dotThis); |
| |
| if (op == JSOP_GETPROP && atom == cx->names().length) { |
| /* Specialize length accesses for the interpreter. */ |
| op = JSOP_LENGTH; |
| } |
| |
| jsatomid index; |
| if (!makeAtomIndex(atom, &index)) |
| return false; |
| |
| return emitIndexOp(op, index); |
| } |
| |
| bool |
| BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op) |
| { |
| MOZ_ASSERT(pn->pn_atom != nullptr); |
| return emitAtomOp(pn->pn_atom, op); |
| } |
| |
| bool |
| BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) |
| { |
| MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); |
| MOZ_ASSERT(index < objectList.length); |
| return emitIndex32(op, index); |
| } |
| |
| bool |
| BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) |
| { |
| return emitInternedObjectOp(objectList.add(objbox), op); |
| } |
| |
| bool |
| BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op) |
| { |
| uint32_t index = objectList.add(objbox1); |
| objectList.add(objbox2); |
| return emitInternedObjectOp(index, op); |
| } |
| |
| bool |
| BytecodeEmitter::emitRegExp(uint32_t index) |
| { |
| return emitIndex32(JSOP_REGEXP, index); |
| } |
| |
| bool |
| BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) |
| { |
| MOZ_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD); |
| MOZ_ASSERT(IsLocalOp(op)); |
| |
| ptrdiff_t off; |
| if (!emitN(op, LOCALNO_LEN, &off)) |
| return false; |
| |
| SET_LOCALNO(code(off), slot); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitUnaliasedVarOp(JSOp op, uint32_t slot, MaybeCheckLexical checkLexical) |
| { |
| MOZ_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD); |
| |
| if (IsLocalOp(op)) { |
| // Only unaliased locals have stack slots assigned to them. Convert the |
| // var index (which includes unaliased and aliased locals) to the stack |
| // slot index. |
| MOZ_ASSERT(localsToFrameSlots_[slot] <= slot); |
| slot = localsToFrameSlots_[slot]; |
| |
| if (checkLexical) { |
| MOZ_ASSERT(op != JSOP_INITLEXICAL); |
| if (!emitLocalOp(JSOP_CHECKLEXICAL, slot)) |
| return false; |
| } |
| |
| return emitLocalOp(op, slot); |
| } |
| |
| MOZ_ASSERT(IsArgOp(op)); |
| ptrdiff_t off; |
| if (!emitN(op, ARGNO_LEN, &off)) |
| return false; |
| |
| SET_ARGNO(code(off), slot); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitScopeCoordOp(JSOp op, ScopeCoordinate sc) |
| { |
| MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD); |
| |
| unsigned n = SCOPECOORD_HOPS_LEN + SCOPECOORD_SLOT_LEN; |
| MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length); |
| |
| ptrdiff_t off; |
| if (!emitN(op, n, &off)) |
| return false; |
| |
| jsbytecode* pc = code(off); |
| SET_SCOPECOORD_HOPS(pc, sc.hops()); |
| pc += SCOPECOORD_HOPS_LEN; |
| SET_SCOPECOORD_SLOT(pc, sc.slot()); |
| pc += SCOPECOORD_SLOT_LEN; |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitAliasedVarOp(JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical) |
| { |
| if (checkLexical) { |
| MOZ_ASSERT(op != JSOP_INITALIASEDLEXICAL); |
| if (!emitScopeCoordOp(JSOP_CHECKALIASEDLEXICAL, sc)) |
| return false; |
| } |
| |
| return emitScopeCoordOp(op, sc); |
| } |
| |
| bool |
| BytecodeEmitter::lookupAliasedName(HandleScript script, PropertyName* name, uint32_t* pslot, |
| ParseNode* pn) |
| { |
| LazyScript::FreeVariable* freeVariables = nullptr; |
| uint32_t lexicalBegin = 0; |
| uint32_t numFreeVariables = 0; |
| if (emitterMode == BytecodeEmitter::LazyFunction) { |
| freeVariables = lazyScript->freeVariables(); |
| lexicalBegin = script->bindings.lexicalBegin(); |
| numFreeVariables = lazyScript->numFreeVariables(); |
| } |
| |
| /* |
| * Beware: BindingIter may contain more than one Binding for a given name |
| * (in the case of |function f(x,x) {}|) but only one will be aliased. |
| */ |
| uint32_t bindingIndex = 0; |
| uint32_t slot = CallObject::RESERVED_SLOTS; |
| for (BindingIter bi(script); !bi.done(); bi++) { |
| if (bi->aliased()) { |
| if (bi->name() == name) { |
| // Check if the free variable from a lazy script was marked as |
| // a possible hoisted use and is a lexical binding. If so, |
| // mark it as such so we emit a dead zone check. |
| if (freeVariables) { |
| for (uint32_t i = 0; i < numFreeVariables; i++) { |
| if (freeVariables[i].atom() == name) { |
| if (freeVariables[i].isHoistedUse() && bindingIndex >= lexicalBegin) { |
| MOZ_ASSERT(pn); |
| MOZ_ASSERT(pn->isUsed()); |
| pn->pn_dflags |= PND_LEXICAL; |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| *pslot = slot; |
| return true; |
| } |
| slot++; |
| } |
| bindingIndex++; |
| } |
| return false; |
| } |
| |
| bool |
| BytecodeEmitter::lookupAliasedNameSlot(PropertyName* name, ScopeCoordinate* sc) |
| { |
| uint32_t slot; |
| if (!lookupAliasedName(script, name, &slot)) |
| return false; |
| |
| sc->setSlot(slot); |
| return true; |
| } |
| |
| static inline MaybeCheckLexical |
| NodeNeedsCheckLexical(ParseNode* pn) |
| { |
| return pn->isHoistedLexicalUse() ? CheckLexical : DontCheckLexical; |
| } |
| |
| static inline JSOp |
| UnaliasedVarOpToAliasedVarOp(JSOp op) |
| { |
| switch (op) { |
| case JSOP_GETARG: case JSOP_GETLOCAL: return JSOP_GETALIASEDVAR; |
| case JSOP_SETARG: case JSOP_SETLOCAL: return JSOP_SETALIASEDVAR; |
| case JSOP_INITLEXICAL: return JSOP_INITALIASEDLEXICAL; |
| default: MOZ_CRASH("unexpected var op"); |
| } |
| } |
| |
| static inline JSOp |
| CheckSetConstOp(JSOp op, ParseNode* pn) |
| { |
| if (pn->resolve()->isConst()) { |
| switch (op) { |
| case JSOP_GETLOCAL: case JSOP_GETALIASEDVAR: break; |
| case JSOP_INITLEXICAL: case JSOP_INITALIASEDLEXICAL: break; |
| case JSOP_SETLOCAL: return JSOP_THROWSETCONST; |
| case JSOP_SETALIASEDVAR: return JSOP_THROWSETALIASEDCONST; |
| default: MOZ_CRASH("unexpected set var op"); |
| } |
| } |
| return op; |
| } |
| |
| bool |
| BytecodeEmitter::emitVarOp(ParseNode* pn, JSOp op) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_NAME)); |
| MOZ_ASSERT(!pn->pn_scopecoord.isFree()); |
| |
| if (pn->isDefn()) { |
| // The hop count needs to be computed even for definitions, due to |
| // cases like |
| // |
| // function outer() { |
| // function inner() { x = 42; } |
| // try { |
| // } catch (e) { |
| // G = function () { e = 43; }; |
| // var x; |
| // } |
| // } |
| // |
| // The definition for x is emitted when the block scope for the catch |
| // is innermost. Moreover, that block scope has aliased bindings, so |
| // there is a non-0 hop count. |
| if (pn->pn_scopecoord.isHopsUnknown()) { |
| BytecodeEmitter* bceOfDef; |
| uint32_t hops = computeHops(pn, &bceOfDef); |
| MOZ_ASSERT(bceOfDef == this); |
| if (!pn->pn_scopecoord.setHops(parser->tokenStream, hops)) |
| return false; |
| } |
| |
| #ifdef DEBUG |
| BytecodeEmitter* bceOfDef; |
| uint32_t hops = computeHops(pn, &bceOfDef); |
| MOZ_ASSERT(bceOfDef == this); |
| MOZ_ASSERT(hops == pn->pn_scopecoord.hops()); |
| #endif |
| |
| if (!computeDefinitionIsAliased(this, pn->resolve(), &op)) |
| return false; |
| } |
| |
| // Aliased names had their JSOp changed by bindNameToSlot or above. |
| if (IsAliasedVarOp(op)) { |
| ScopeCoordinate sc; |
| sc.setHops(pn->pn_scopecoord.hops()); |
| sc.setSlot(pn->pn_scopecoord.slot()); |
| return emitAliasedVarOp(CheckSetConstOp(op, pn), sc, NodeNeedsCheckLexical(pn)); |
| } |
| |
| #ifdef DEBUG |
| BytecodeEmitter* bceOfDef; |
| // Call computeHops to get bceOfDef. |
| (void) computeHops(pn, &bceOfDef); |
| MOZ_ASSERT(!isAliasedName(bceOfDef, pn)); |
| #endif |
| MOZ_ASSERT_IF(pn->isKind(PNK_NAME), IsArgOp(op) || IsLocalOp(op)); |
| MOZ_ASSERT(pn->isUsed() || pn->isDefn()); |
| return emitUnaliasedVarOp(CheckSetConstOp(op, pn), pn->pn_scopecoord.slot(), |
| NodeNeedsCheckLexical(pn)); |
| } |
| |
| static JSOp |
| GetIncDecInfo(ParseNodeKind kind, bool* post) |
| { |
| MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT || |
| kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT); |
| *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT; |
| return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB; |
| } |
| |
| bool |
| BytecodeEmitter::emitVarIncDec(ParseNode* pn) |
| { |
| JSOp op = pn->pn_kid->getOp(); |
| MOZ_ASSERT(IsArgOp(op) || IsLocalOp(op) || IsAliasedVarOp(op)); |
| MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); |
| MOZ_ASSERT(!pn->pn_kid->pn_scopecoord.isFree()); |
| |
| bool post; |
| JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
| |
| JSOp getOp, setOp; |
| if (IsLocalOp(op)) { |
| getOp = JSOP_GETLOCAL; |
| setOp = JSOP_SETLOCAL; |
| } else if (IsArgOp(op)) { |
| getOp = JSOP_GETARG; |
| setOp = JSOP_SETARG; |
| } else { |
| getOp = JSOP_GETALIASEDVAR; |
| setOp = JSOP_SETALIASEDVAR; |
| } |
| |
| if (!emitVarOp(pn->pn_kid, getOp)) // V |
| return false; |
| if (!emit1(JSOP_POS)) // N |
| return false; |
| if (post && !emit1(JSOP_DUP)) // N? N |
| return false; |
| if (!emit1(JSOP_ONE)) // N? N 1 |
| return false; |
| if (!emit1(binop)) // N? N+1 |
| return false; |
| if (!emitVarOp(pn->pn_kid, setOp)) // N? N+1 |
| return false; |
| if (post && !emit1(JSOP_POP)) // RESULT |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::atBodyLevel() const |
| { |
| // 'eval' and non-syntactic scripts are always under an invisible lexical |
| // scope, but since it is not syntactic, it should still be considered at |
| // body level. |
| if (sc->staticScope()->is<StaticEvalObject>()) { |
| bool bl = !innermostStmt()->enclosing; |
| MOZ_ASSERT_IF(bl, innermostStmt()->type == StmtType::BLOCK); |
| MOZ_ASSERT_IF(bl, innermostStmt()->staticScope |
| ->as<StaticBlockObject>() |
| .enclosingStaticScope() == sc->staticScope()); |
| return bl; |
| } |
| return !innermostStmt() || sc->isModuleBox(); |
| } |
| |
| uint32_t |
| BytecodeEmitter::computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut) |
| { |
| Definition* dn = pn->resolve(); |
| MOZ_ASSERT(dn->isDefn()); |
| MOZ_ASSERT(!dn->isPlaceholder()); |
| MOZ_ASSERT(dn->isBound()); |
| |
| uint32_t hops = 0; |
| BytecodeEmitter* bceOfDef = this; |
| StaticScopeIter<NoGC> ssi(innermostStaticScope()); |
| JSObject* defScope = blockScopeOfDef(dn); |
| while (ssi.staticScope() != defScope) { |
| if (ssi.hasSyntacticDynamicScopeObject()) |
| hops++; |
| if (ssi.type() == StaticScopeIter<NoGC>::Function) { |
| MOZ_ASSERT(dn->isClosed()); |
| bceOfDef = bceOfDef->parent; |
| } |
| ssi++; |
| } |
| |
| *bceOfDefOut = bceOfDef; |
| return hops; |
| } |
| |
| bool |
| BytecodeEmitter::isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn) |
| { |
| // If the definition is in another function, it's definitely aliased. |
| if (bceOfDef != this) |
| return true; |
| |
| Definition* dn = pn->resolve(); |
| switch (dn->kind()) { |
| case Definition::LET: |
| case Definition::CONSTANT: |
| /* |
| * There are two ways to alias a let variable: nested functions and |
| * dynamic scope operations. (This is overly conservative since the |
| * bindingsAccessedDynamically flag, checked by allLocalsAliased, is |
| * function-wide.) |
| * |
| * In addition all locals in generators are marked as aliased, to ensure |
| * that they are allocated on scope chains instead of on the stack. See |
| * the definition of SharedContext::allLocalsAliased. |
| */ |
| return dn->isClosed() || sc->allLocalsAliased(); |
| case Definition::ARG: |
| /* |
| * Consult the bindings, since they already record aliasing. We might |
| * be tempted to use the same definition as VAR/CONST/LET, but there is |
| * a problem caused by duplicate arguments: only the last argument with |
| * a given name is aliased. This is necessary to avoid generating a |
| * shape for the call object with with more than one name for a given |
| * slot (which violates internal engine invariants). All this means that |
| * the '|| sc->allLocalsAliased()' disjunct is incorrect since it will |
| * mark both parameters in function(x,x) as aliased. |
| */ |
| return script->formalIsAliased(pn->pn_scopecoord.slot()); |
| case Definition::VAR: |
| MOZ_ASSERT_IF(sc->allLocalsAliased(), script->localIsAliased(pn->pn_scopecoord.slot())); |
| return script->localIsAliased(pn->pn_scopecoord.slot()); |
| case Definition::PLACEHOLDER: |
| case Definition::NAMED_LAMBDA: |
| case Definition::MISSING: |
| case Definition::IMPORT: |
| MOZ_CRASH("unexpected dn->kind"); |
| } |
| return false; |
| } |
| |
| bool |
| BytecodeEmitter::computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op) |
| { |
| if (dn->isKnownAliased()) { |
| *op = UnaliasedVarOpToAliasedVarOp(*op); |
| } else if (isAliasedName(bceOfDef, dn)) { |
| // Translate the frame slot to a slot on the dynamic scope |
| // object. Aliased block bindings do not need adjusting; see |
| // computeAliasedSlots. |
| uint32_t slot = dn->pn_scopecoord.slot(); |
| if (blockScopeOfDef(dn)->is<JSFunction>() || |
| blockScopeOfDef(dn)->is<ModuleObject>()) |
| { |
| MOZ_ASSERT(IsArgOp(*op) || slot < bceOfDef->script->bindings.numBodyLevelLocals()); |
| MOZ_ALWAYS_TRUE(bceOfDef->lookupAliasedName(bceOfDef->script, dn->name(), &slot)); |
| } |
| if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot)) |
| return false; |
| |
| *op = UnaliasedVarOpToAliasedVarOp(*op); |
| |
| // Mark the definition as having already computed alias information. |
| dn->pn_dflags |= PND_KNOWNALIASED; |
| } |
| |
| return true; |
| } |
| |
| JSOp |
| BytecodeEmitter::strictifySetNameOp(JSOp op) |
| { |
| switch (op) { |
| case JSOP_SETNAME: |
| if (sc->strict()) |
| op = JSOP_STRICTSETNAME; |
| break; |
| case JSOP_SETGNAME: |
| if (sc->strict()) |
| op = JSOP_STRICTSETGNAME; |
| break; |
| default:; |
| } |
| return op; |
| } |
| |
| void |
| BytecodeEmitter::strictifySetNameNode(ParseNode* pn) |
| { |
| pn->setOp(strictifySetNameOp(pn->getOp())); |
| } |
| |
| /* |
| * Try to convert a *NAME op with a free name to a more specialized GNAME, |
| * INTRINSIC or ALIASEDVAR op, which optimize accesses on that name. |
| * Return true if a conversion was made. |
| */ |
| bool |
| BytecodeEmitter::tryConvertFreeName(ParseNode* pn) |
| { |
| /* |
| * In self-hosting mode, JSOP_*NAME is unconditionally converted to |
| * JSOP_*INTRINSIC. This causes lookups to be redirected to the special |
| * intrinsics holder in the global object, into which any missing values are |
| * cloned lazily upon first access. |
| */ |
| if (emitterMode == BytecodeEmitter::SelfHosting) { |
| JSOp op; |
| switch (pn->getOp()) { |
| case JSOP_GETNAME: op = JSOP_GETINTRINSIC; break; |
| case JSOP_SETNAME: op = JSOP_SETINTRINSIC; break; |
| /* Other *NAME ops aren't (yet) supported in self-hosted code. */ |
| default: MOZ_CRASH("intrinsic"); |
| } |
| pn->setOp(op); |
| return true; |
| } |
| |
| /* |
| * When parsing inner functions lazily, parse nodes for outer functions no |
| * longer exist and only the function's scope chain is available for |
| * resolving upvar accesses within the inner function. |
| */ |
| if (emitterMode == BytecodeEmitter::LazyFunction) { |
| // The only statements within a lazy function which can push lexical |
| // scopes are try/catch blocks. Use generic ops in this case. |
| for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) { |
| if (stmt->type == StmtType::CATCH) |
| return true; |
| } |
| |
| // Walk the static scope chain and look for an aliased binding with |
| // the name pn->pn_atom. |
| uint32_t hops = 0; |
| Maybe<uint32_t> slot; |
| FunctionBox* funbox = sc->asFunctionBox(); |
| PropertyName* name = pn->pn_atom->asPropertyName(); |
| for (StaticScopeIter<NoGC> ssi(funbox->staticScope()); !ssi.done(); ssi++) { |
| // Don't optimize names through non-global eval. For global eval |
| // we can use GNAME ops. |
| if (ssi.type() == StaticScopeIter<NoGC>::Eval) { |
| if (ssi.eval().isNonGlobal()) |
| return false; |
| MOZ_ASSERT(!slot.isSome()); |
| break; |
| } |
| |
| if (!ssi.hasSyntacticDynamicScopeObject()) |
| continue; |
| |
| // Look up for name in function and block scopes. |
| if (ssi.type() == StaticScopeIter<NoGC>::Function) { |
| RootedScript funScript(cx, ssi.funScript()); |
| if (funScript->funHasExtensibleScope() || ssi.fun().atom() == pn->pn_atom) |
| return false; |
| |
| // Skip the current function, since we're trying to convert a |
| // free name. |
| if (script != funScript) { |
| uint32_t slot_; |
| if (lookupAliasedName(funScript, name, &slot_, pn)) { |
| slot = Some(slot_); |
| break; |
| } |
| } |
| } else if (ssi.type() == StaticScopeIter<NoGC>::Module) { |
| RootedScript moduleScript(cx, ssi.moduleScript()); |
| uint32_t slot_; |
| if (lookupAliasedName(moduleScript, name, &slot_, pn)) { |
| slot = Some(slot_); |
| break; |
| } |
| |
| // Convert module import accesses to use JSOP_GETIMPORT. |
| RootedModuleEnvironmentObject env(cx, ssi.module().environment()); |
| RootedPropertyName propName(cx, name); |
| MOZ_ASSERT(env); |
| if (env->hasImportBinding(propName)) { |
| if (pn->getOp() == JSOP_GETNAME) { |
| pn->setOp(JSOP_GETIMPORT); |
| return true; |
| } |
| return false; |
| } |
| } else if (ssi.type() == StaticScopeIter<NoGC>::Block) { |
| RootedShape shape(cx, ssi.block().lookupAliasedName(name)); |
| if (shape) { |
| // Don't optimize setting a 'const' binding. Let the slow |
| // path do the error checking. |
| if (!shape->writable() && pn->getOp() == JSOP_SETNAME) |
| return false; |
| slot = Some(shape->slot()); |
| pn->pn_dflags |= PND_LEXICAL; |
| break; |
| } |
| } else { |
| MOZ_ASSERT(ssi.type() != StaticScopeIter<NoGC>::With); |
| } |
| |
| hops++; |
| } |
| |
| // If we found a scope binding name, convert the name op to an aliased |
| // var op. |
| if (slot.isSome()) { |
| JSOp op; |
| switch (pn->getOp()) { |
| case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break; |
| case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break; |
| default: return false; |
| } |
| pn->setOp(op); |
| MOZ_ALWAYS_TRUE(pn->pn_scopecoord.set(parser->tokenStream, hops, *slot)); |
| return true; |
| } |
| } |
| |
| // Unbound names aren't recognizable global-property references if the |
| // script is inside a non-global eval call. |
| if (insideNonGlobalEval) |
| return false; |
| |
| // If we are inside a module then unbound names in a function may refer to |
| // imports, so we can't use GNAME ops here. |
| if (insideModule) |
| return false; |
| |
| // Skip trying to use GNAME ops if we know our script has a non-syntactic |
| // scope, since they'll just get treated as NAME ops anyway. |
| if (script->hasNonSyntacticScope()) |
| return false; |
| |
| // Deoptimized names also aren't necessarily globals. |
| if (pn->isDeoptimized()) |
| return false; |
| |
| if (sc->isFunctionBox()) { |
| // Unbound names in function code may not be globals if new locals can |
| // be added to this function (or an enclosing one) to alias a global |
| // reference. |
| FunctionBox* funbox = sc->asFunctionBox(); |
| if (funbox->mightAliasLocals()) |
| return false; |
| } |
| |
| // If this is eval code, being evaluated inside strict mode eval code, |
| // an "unbound" name might be a binding local to that outer eval: |
| // |
| // var x = "GLOBAL"; |
| // eval('"use strict"; ' + |
| // 'var x; ' + |
| // 'eval("print(x)");'); // "undefined", not "GLOBAL" |
| // |
| // Given the enclosing eval code's strictness and its bindings (neither is |
| // readily available now), we could exactly check global-ness, but it's not |
| // worth the trouble for doubly-nested eval code. So we conservatively |
| // approximate. If the outer eval code is strict, then this eval code will |
| // be: thus, don't optimize if we're compiling strict code inside an eval. |
| // |
| // Though actually, we don't even need an inner eval. We could just as well |
| // have a lambda inside that outer strict mode eval and it would run into |
| // the same issue. |
| if (insideEval && sc->strict()) |
| return false; |
| |
| JSOp op; |
| switch (pn->getOp()) { |
| case JSOP_GETNAME: op = JSOP_GETGNAME; break; |
| case JSOP_SETNAME: op = strictifySetNameOp(JSOP_SETGNAME); break; |
| default: MOZ_CRASH("gname"); |
| } |
| pn->setOp(op); |
| MOZ_ASSERT_IF(op == JSOP_INITGLEXICAL, |
| IsStaticGlobalLexicalScope(blockScopeOfDef(pn->resolve()))); |
| return true; |
| } |
| |
| /* |
| * BindNameToSlotHelper attempts to optimize name gets and sets to stack slot |
| * loads and stores, given the compile-time information in |this| and a PNK_NAME |
| * node pn. It returns false on error, true on success. |
| * |
| * The caller can test pn->pn_scopecoord.isFree() to tell whether optimization |
| * occurred, in which case bindNameToSlotHelper also updated pn->pn_op. If |
| * pn->pn_cookie.isFree() is still true on return, pn->pn_op still may have |
| * been optimized, e.g., from JSOP_GETNAME to JSOP_CALLEE. Whether or not |
| * pn->pn_op was modified, if this function finds an argument or local variable |
| * name, PND_CONST will be set in pn_dflags for read-only properties after a |
| * successful return. |
| * |
| * NB: if you add more opcodes specialized from JSOP_GETNAME, etc., don't forget |
| * to update the special cases in EmitFor (for-in) and emitAssignment (= and |
| * op=, e.g. +=). |
| */ |
| bool |
| BytecodeEmitter::bindNameToSlotHelper(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_NAME)); |
| |
| /* Don't attempt if 'pn' is already bound or deoptimized or a function. */ |
| if (pn->isBound() || pn->isDeoptimized()) |
| return true; |
| |
| /* JSOP_CALLEE is pre-bound by definition. */ |
| JSOp op = pn->getOp(); |
| MOZ_ASSERT(op != JSOP_CALLEE); |
| MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
| |
| /* |
| * The parser already linked name uses to definitions when (where not |
| * prevented by non-lexical constructs like 'with' and 'eval'). |
| */ |
| Definition* dn; |
| if (pn->isUsed()) { |
| MOZ_ASSERT(pn->pn_scopecoord.isFree()); |
| dn = pn->pn_lexdef; |
| MOZ_ASSERT(dn->isDefn()); |
| pn->pn_dflags |= (dn->pn_dflags & PND_CONST); |
| } else if (pn->isDefn()) { |
| dn = (Definition*) pn; |
| } else { |
| return true; |
| } |
| |
| if (dn->pn_scopecoord.isFree()) { |
| if (evalCaller) { |
| MOZ_ASSERT(script->treatAsRunOnce() || sc->isFunctionBox()); |
| |
| /* |
| * Don't generate upvars on the left side of a for loop. See |
| * bug 470758. |
| */ |
| if (emittingForInit) |
| return true; |
| |
| /* |
| * If this is an eval in the global scope, then unbound variables |
| * must be globals, so try to use GNAME ops. |
| */ |
| if (!evalCaller->functionOrCallerFunction() && tryConvertFreeName(pn)) { |
| pn->pn_dflags |= PND_BOUND; |
| return true; |
| } |
| |
| /* |
| * Out of tricks, so we must rely on PICs to optimize named |
| * accesses from direct eval called from function code. |
| */ |
| return true; |
| } |
| |
| /* Optimize accesses to undeclared globals. */ |
| if (!tryConvertFreeName(pn)) |
| return true; |
| |
| pn->pn_dflags |= PND_BOUND; |
| return true; |
| } |
| |
| /* |
| * At this point, we are only dealing with uses that have already been |
| * bound to definitions via pn_lexdef. The rest of this routine converts |
| * the parse node of the use from its initial JSOP_*NAME* op to a |
| * LOCAL/ARG op. This requires setting the node's pn_scopecoord with a |
| * pair (hops, slot) where 'hops' is the number of dynamic scopes between |
| * the use and the def and 'slot' is the index to emit as the immediate of |
| * the ARG/LOCAL op. For example, in this code: |
| * |
| * function(a,b,x) { return x } |
| * function(y) { function() { return y } } |
| * |
| * x will get (hops = 0, slot = 2) and y will get (hops = 1, slot = 0). |
| */ |
| MOZ_ASSERT(!pn->isDefn()); |
| MOZ_ASSERT(pn->isUsed()); |
| MOZ_ASSERT(pn->pn_lexdef); |
| MOZ_ASSERT(pn->pn_scopecoord.isFree()); |
| |
| /* |
| * We are compiling a function body and may be able to optimize name |
| * to stack slot. Look for an argument or variable in the function and |
| * rewrite pn_op and update pn accordingly. |
| */ |
| switch (dn->kind()) { |
| case Definition::ARG: |
| switch (op) { |
| case JSOP_GETNAME: |
| op = JSOP_GETARG; break; |
| case JSOP_SETNAME: |
| case JSOP_STRICTSETNAME: |
| op = JSOP_SETARG; break; |
| default: MOZ_CRASH("arg"); |
| } |
| MOZ_ASSERT(!pn->isConst()); |
| break; |
| |
| case Definition::VAR: |
| case Definition::CONSTANT: |
| case Definition::LET: |
| switch (op) { |
| case JSOP_GETNAME: |
| op = JSOP_GETLOCAL; break; |
| case JSOP_SETNAME: |
| case JSOP_STRICTSETNAME: |
| op = JSOP_SETLOCAL; break; |
| default: MOZ_CRASH("local"); |
| } |
| break; |
| |
| case Definition::NAMED_LAMBDA: { |
| MOZ_ASSERT(dn->isOp(JSOP_CALLEE)); |
| MOZ_ASSERT(op != JSOP_CALLEE); |
| |
| /* |
| * Currently, the ALIASEDVAR ops do not support accessing the |
| * callee of a DeclEnvObject, so use NAME. |
| */ |
| JSFunction* fun = sc->asFunctionBox()->function(); |
| if (blockScopeOfDef(dn) != fun) |
| return true; |
| |
| MOZ_ASSERT(fun->isLambda()); |
| MOZ_ASSERT(pn->pn_atom == fun->atom()); |
| |
| /* |
| * Leave pn->isOp(JSOP_GETNAME) if this->fun needs a CallObject to |
| * address two cases: a new binding introduced by eval, and |
| * assignment to the name in strict mode. |
| * |
| * var fun = (function f(s) { eval(s); return f; }); |
| * assertEq(fun("var f = 42"), 42); |
| * |
| * ECMAScript specifies that a function expression's name is bound |
| * in a lexical environment distinct from that used to bind its |
| * named parameters, the arguments object, and its variables. The |
| * new binding for "var f = 42" shadows the binding for the |
| * function itself, so the name of the function will not refer to |
| * the function. |
| * |
| * (function f() { "use strict"; f = 12; })(); |
| * |
| * Outside strict mode, assignment to a function expression's name |
| * has no effect. But in strict mode, this attempt to mutate an |
| * immutable binding must throw a TypeError. We implement this by |
| * not optimizing such assignments and by marking such functions as |
| * needsCallObject, ensuring that the function name is represented in |
| * the scope chain so that assignment will throw a TypeError. |
| */ |
| if (!sc->asFunctionBox()->needsCallObject()) { |
| op = JSOP_CALLEE; |
| pn->pn_dflags |= PND_CONST; |
| } |
| |
| pn->setOp(op); |
| pn->pn_dflags |= PND_BOUND; |
| return true; |
| } |
| |
| case Definition::PLACEHOLDER: |
| return true; |
| |
| case Definition::IMPORT: |
| if (op == JSOP_GETNAME) |
| pn->setOp(JSOP_GETIMPORT); |
| return true; |
| |
| case Definition::MISSING: |
| MOZ_CRASH("unexpected definition kind"); |
| } |
| |
| // The hop count is the number of dynamic scopes during execution that must |
| // be skipped to access the binding. |
| BytecodeEmitter* bceOfDef; |
| uint32_t slot = dn->pn_scopecoord.slot(); |
| uint32_t hops = computeHops(pn, &bceOfDef); |
| |
| /* |
| * Explicitly disallow accessing var/let bindings in global scope from |
| * nested functions. The reason for this limitation is that, since the |
| * global script is not included in the static scope chain (1. because it |
| * has no object to stand in the static scope chain, 2. to minimize memory |
| * bloat where a single live function keeps its whole global script |
| * alive.), ScopeCoordinateToTypeSet is not able to find the var/let's |
| * associated TypeSet. |
| */ |
| if (bceOfDef != this && bceOfDef->sc->isGlobalContext()) |
| return true; |
| |
| if (!pn->pn_scopecoord.set(parser->tokenStream, hops, slot)) |
| return false; |
| |
| if (!computeDefinitionIsAliased(bceOfDef, dn, &op)) |
| return false; |
| |
| // Re-set the slot on if it is aliased, since the slot would have been |
| // translated on dn. |
| if (IsAliasedVarOp(op)) { |
| MOZ_ASSERT(dn->isKnownAliased()); |
| pn->pn_scopecoord.setSlot(parser->tokenStream, dn->pn_scopecoord.slot()); |
| } |
| |
| MOZ_ASSERT(!pn->isOp(op)); |
| pn->setOp(op); |
| pn->pn_dflags |= PND_BOUND; |
| return true; |
| } |
| |
| /* |
| * Attempts to bind the name, then checks that no dynamic scope lookup ops are |
| * emitted in self-hosting mode. NAME ops do lookups off current scope chain, |
| * and we do not want to allow self-hosted code to use the dynamic scope. |
| */ |
| bool |
| BytecodeEmitter::bindNameToSlot(ParseNode* pn) |
| { |
| if (!bindNameToSlotHelper(pn)) |
| return false; |
| |
| strictifySetNameNode(pn); |
| |
| if (emitterMode == BytecodeEmitter::SelfHosting && !pn->isBound()) { |
| reportError(pn, JSMSG_SELFHOSTED_UNBOUND_NAME); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) |
| { |
| JS_CHECK_RECURSION(cx, return false); |
| |
| restart: |
| |
| switch (pn->getKind()) { |
| // Trivial cases with no side effects. |
| case PNK_NOP: |
| case PNK_STRING: |
| case PNK_TEMPLATE_STRING: |
| case PNK_REGEXP: |
| case PNK_TRUE: |
| case PNK_FALSE: |
| case PNK_NULL: |
| case PNK_ELISION: |
| case PNK_GENERATOR: |
| case PNK_NUMBER: |
| case PNK_OBJECT_PROPERTY_NAME: |
| MOZ_ASSERT(pn->isArity(PN_NULLARY)); |
| *answer = false; |
| return true; |
| |
| // |this| can throw in derived class constructors, including nested arrow |
| // functions or eval. |
| case PNK_THIS: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = sc->needsThisTDZChecks(); |
| return true; |
| |
| // Trivial binary nodes with more token pos holders. |
| case PNK_NEWTARGET: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); |
| MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); |
| *answer = false; |
| return true; |
| |
| case PNK_BREAK: |
| case PNK_CONTINUE: |
| case PNK_DEBUGGER: |
| MOZ_ASSERT(pn->isArity(PN_NULLARY)); |
| *answer = true; |
| return true; |
| |
| // Watch out for getters! |
| case PNK_DOT: |
| MOZ_ASSERT(pn->isArity(PN_NAME)); |
| *answer = true; |
| return true; |
| |
| // Unary cases with side effects only if the child has them. |
| case PNK_TYPEOFEXPR: |
| case PNK_VOID: |
| case PNK_NOT: |
| case PNK_COMPUTED_NAME: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| return checkSideEffects(pn->pn_kid, answer); |
| |
| // Looking up or evaluating the associated name could throw. |
| case PNK_TYPEOFNAME: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| // These unary cases have side effects on the enclosing object/array, |
| // sure. But that's not the question this function answers: it's |
| // whether the operation may have a side effect on something *other* than |
| // the result of the overall operation in which it's embedded. The |
| // answer to that is no, for an object literal having a mutated prototype |
| // and an array comprehension containing no other effectful operations |
| // only produce a value, without affecting anything else. |
| case PNK_MUTATEPROTO: |
| case PNK_ARRAYPUSH: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| return checkSideEffects(pn->pn_kid, answer); |
| |
| // Unary cases with obvious side effects. |
| case PNK_PREINCREMENT: |
| case PNK_POSTINCREMENT: |
| case PNK_PREDECREMENT: |
| case PNK_POSTDECREMENT: |
| case PNK_THROW: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| // These might invoke valueOf/toString, even with a subexpression without |
| // side effects! Consider |+{ valueOf: null, toString: null }|. |
| case PNK_BITNOT: |
| case PNK_POS: |
| case PNK_NEG: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| // This invokes the (user-controllable) iterator protocol. |
| case PNK_SPREAD: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| case PNK_YIELD_STAR: |
| case PNK_YIELD: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| // Deletion generally has side effects, even if isolated cases have none. |
| case PNK_DELETENAME: |
| case PNK_DELETEPROP: |
| case PNK_DELETEELEM: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| // Deletion of a non-Reference expression has side effects only through |
| // evaluating the expression. |
| case PNK_DELETEEXPR: { |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| ParseNode* expr = pn->pn_kid; |
| return checkSideEffects(expr, answer); |
| } |
| |
| case PNK_SEMI: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| if (ParseNode* expr = pn->pn_kid) |
| return checkSideEffects(expr, answer); |
| *answer = false; |
| return true; |
| |
| // Binary cases with obvious side effects. |
| case PNK_ASSIGN: |
| case PNK_ADDASSIGN: |
| case PNK_SUBASSIGN: |
| case PNK_BITORASSIGN: |
| case PNK_BITXORASSIGN: |
| case PNK_BITANDASSIGN: |
| case PNK_LSHASSIGN: |
| case PNK_RSHASSIGN: |
| case PNK_URSHASSIGN: |
| case PNK_MULASSIGN: |
| case PNK_DIVASSIGN: |
| case PNK_MODASSIGN: |
| case PNK_POWASSIGN: |
| case PNK_SETTHIS: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| case PNK_STATEMENTLIST: |
| case PNK_CATCHLIST: |
| // Strict equality operations and logical operators are well-behaved and |
| // perform no conversions. |
| case PNK_OR: |
| case PNK_AND: |
| case PNK_STRICTEQ: |
| case PNK_STRICTNE: |
| // Any subexpression of a comma expression could be effectful. |
| case PNK_COMMA: |
| MOZ_ASSERT(pn->pn_count > 0); |
| // Subcomponents of a literal may be effectful. |
| case PNK_ARRAY: |
| case PNK_OBJECT: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| for (ParseNode* item = pn->pn_head; item; item = item->pn_next) { |
| if (!checkSideEffects(item, answer)) |
| return false; |
| if (*answer) |
| return true; |
| } |
| return true; |
| |
| // Most other binary operations (parsed as lists in SpiderMonkey) may |
| // perform conversions triggering side effects. Math operations perform |
| // ToNumber and may fail invoking invalid user-defined toString/valueOf: |
| // |5 < { toString: null }|. |instanceof| throws if provided a |
| // non-object constructor: |null instanceof null|. |in| throws if given |
| // a non-object RHS: |5 in null|. |
| case PNK_BITOR: |
| case PNK_BITXOR: |
| case PNK_BITAND: |
| case PNK_EQ: |
| case PNK_NE: |
| case PNK_LT: |
| case PNK_LE: |
| case PNK_GT: |
| case PNK_GE: |
| case PNK_INSTANCEOF: |
| case PNK_IN: |
| case PNK_LSH: |
| case PNK_RSH: |
| case PNK_URSH: |
| case PNK_ADD: |
| case PNK_SUB: |
| case PNK_STAR: |
| case PNK_DIV: |
| case PNK_MOD: |
| case PNK_POW: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| MOZ_ASSERT(pn->pn_count >= 2); |
| *answer = true; |
| return true; |
| |
| case PNK_COLON: |
| case PNK_CASE: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| if (!checkSideEffects(pn->pn_left, answer)) |
| return false; |
| if (*answer) |
| return true; |
| return checkSideEffects(pn->pn_right, answer); |
| |
| // More getters. |
| case PNK_ELEM: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| // These affect visible names in this code, or in other code. |
| case PNK_IMPORT: |
| case PNK_EXPORT_FROM: |
| case PNK_EXPORT_DEFAULT: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| // Likewise. |
| case PNK_EXPORT: |
| MOZ_ASSERT(pn->isArity(PN_UNARY)); |
| *answer = true; |
| return true; |
| |
| // Every part of a loop might be effect-free, but looping infinitely *is* |
| // an effect. (Language lawyer trivia: C++ says threads can be assumed |
| // to exit or have side effects, C++14 [intro.multithread]p27, so a C++ |
| // implementation's equivalent of the below could set |*answer = false;| |
| // if all loop sub-nodes set |*answer = false|!) |
| case PNK_DOWHILE: |
| case PNK_WHILE: |
| case PNK_FOR: |
| case PNK_COMPREHENSIONFOR: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| // Declarations affect the name set of the relevant scope. |
| case PNK_VAR: |
| case PNK_CONST: |
| case PNK_LET: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| *answer = true; |
| return true; |
| |
| case PNK_IF: |
| case PNK_CONDITIONAL: |
| MOZ_ASSERT(pn->isArity(PN_TERNARY)); |
| if (!checkSideEffects(pn->pn_kid1, answer)) |
| return false; |
| if (*answer) |
| return true; |
| if (!checkSideEffects(pn->pn_kid2, answer)) |
| return false; |
| if (*answer) |
| return true; |
| if ((pn = pn->pn_kid3)) |
| goto restart; |
| return true; |
| |
| // Function calls can invoke non-local code. |
| case PNK_NEW: |
| case PNK_CALL: |
| case PNK_TAGGED_TEMPLATE: |
| case PNK_SUPERCALL: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| *answer = true; |
| return true; |
| |
| // Classes typically introduce names. Even if no name is introduced, |
| // the heritage and/or class body (through computed property names) |
| // usually have effects. |
| case PNK_CLASS: |
| MOZ_ASSERT(pn->isArity(PN_TERNARY)); |
| *answer = true; |
| return true; |
| |
| // |with| calls |ToObject| on its expression and so throws if that value |
| // is null/undefined. |
| case PNK_WITH: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| case PNK_RETURN: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| case PNK_NAME: |
| MOZ_ASSERT(pn->isArity(PN_NAME)); |
| *answer = true; |
| return true; |
| |
| // Shorthands could trigger getters: the |x| in the object literal in |
| // |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers |
| // one. (Of course, it isn't necessary to use |with| for a shorthand to |
| // trigger a getter.) |
| case PNK_SHORTHAND: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| *answer = true; |
| return true; |
| |
| case PNK_FUNCTION: |
| MOZ_ASSERT(pn->isArity(PN_CODE)); |
| /* |
| * A named function, contrary to ES3, is no longer effectful, because |
| * we bind its name lexically (using JSOP_CALLEE) instead of creating |
| * an Object instance and binding a readonly, permanent property in it |
| * (the object and binding can be detected and hijacked or captured). |
| * This is a bug fix to ES3; it is fixed in ES3.1 drafts. |
| */ |
| *answer = false; |
| return true; |
| |
| case PNK_MODULE: |
| *answer = false; |
| return true; |
| |
| // Generator expressions have no side effects on their own. |
| case PNK_GENEXP: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| *answer = false; |
| return true; |
| |
| case PNK_TRY: |
| MOZ_ASSERT(pn->isArity(PN_TERNARY)); |
| if (!checkSideEffects(pn->pn_kid1, answer)) |
| return false; |
| if (*answer) |
| return true; |
| if (ParseNode* catchList = pn->pn_kid2) { |
| MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); |
| if (!checkSideEffects(catchList, answer)) |
| return false; |
| if (*answer) |
| return true; |
| } |
| if (ParseNode* finallyBlock = pn->pn_kid3) { |
| if (!checkSideEffects(finallyBlock, answer)) |
| return false; |
| } |
| return true; |
| |
| case PNK_CATCH: |
| MOZ_ASSERT(pn->isArity(PN_TERNARY)); |
| if (!checkSideEffects(pn->pn_kid1, answer)) |
| return false; |
| if (*answer) |
| return true; |
| if (ParseNode* cond = pn->pn_kid2) { |
| if (!checkSideEffects(cond, answer)) |
| return false; |
| if (*answer) |
| return true; |
| } |
| return checkSideEffects(pn->pn_kid3, answer); |
| |
| case PNK_SWITCH: |
| case PNK_LETBLOCK: |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| if (!checkSideEffects(pn->pn_left, answer)) |
| return false; |
| return *answer || checkSideEffects(pn->pn_right, answer); |
| |
| case PNK_LABEL: |
| case PNK_LEXICALSCOPE: |
| MOZ_ASSERT(pn->isArity(PN_NAME)); |
| return checkSideEffects(pn->expr(), answer); |
| |
| // We could methodically check every interpolated expression, but it's |
| // probably not worth the trouble. Treat template strings as effect-free |
| // only if they don't contain any substitutions. |
| case PNK_TEMPLATE_STRING_LIST: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| MOZ_ASSERT(pn->pn_count > 0); |
| MOZ_ASSERT((pn->pn_count % 2) == 1, |
| "template strings must alternate template and substitution " |
| "parts"); |
| *answer = pn->pn_count > 1; |
| return true; |
| |
| case PNK_ARRAYCOMP: |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| MOZ_ASSERT(pn->pn_count == 1); |
| return checkSideEffects(pn->pn_head, answer); |
| |
| case PNK_ARGSBODY: |
| *answer = true; |
| return true; |
| |
| case PNK_FORIN: // by PNK_FOR/PNK_COMPREHENSIONFOR |
| case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR |
| case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR |
| case PNK_CLASSMETHOD: // by PNK_CLASS |
| case PNK_CLASSNAMES: // by PNK_CLASS |
| case PNK_CLASSMETHODLIST: // by PNK_CLASS |
| case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT |
| case PNK_IMPORT_SPEC: // by PNK_IMPORT |
| case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT |
| case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT |
| case PNK_EXPORT_SPEC: // by PNK_EXPORT |
| case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE |
| case PNK_POSHOLDER: // by PNK_NEWTARGET |
| case PNK_SUPERBASE: // by PNK_ELEM and others |
| MOZ_CRASH("handled by parent nodes"); |
| |
| case PNK_LIMIT: // invalid sentinel value |
| MOZ_CRASH("invalid node kind"); |
| } |
| |
| MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in " |
| "BytecodeEmitter::checkSideEffects"); |
| } |
| |
| bool |
| BytecodeEmitter::isInLoop() |
| { |
| for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) { |
| if (stmt->isLoop()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| BytecodeEmitter::checkSingletonContext() |
| { |
| if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop()) |
| return false; |
| hasSingletons = true; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::checkRunOnceContext() |
| { |
| return checkSingletonContext() || (!isInLoop() && isRunOnceLambda()); |
| } |
| |
| bool |
| BytecodeEmitter::needsImplicitThis() |
| { |
| // Short-circuit if there is an enclosing 'with' static scope. |
| if (sc->inWith()) |
| return true; |
| |
| // Otherwise walk the statement stack. |
| for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) { |
| if (stmt->type == StmtType::WITH) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void |
| BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx) |
| { |
| // Note: when parsing off thread the resulting scripts need to be handed to |
| // the debugger after rejoining to the main thread. |
| if (!cx->isJSContext()) |
| return; |
| |
| // Lazy scripts are never top level (despite always being invoked with a |
| // nullptr parent), and so the hook should never be fired. |
| if (emitterMode != LazyFunction && !parent) { |
| Debugger::onNewScript(cx->asJSContext(), script); |
| } |
| } |
| |
| inline TokenStream* |
| BytecodeEmitter::tokenStream() |
| { |
| return &parser->tokenStream; |
| } |
| |
| bool |
| BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) |
| { |
| TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
| |
| va_list args; |
| va_start(args, errorNumber); |
| bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR, |
| errorNumber, args); |
| va_end(args); |
| return result; |
| } |
| |
| bool |
| BytecodeEmitter::reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...) |
| { |
| TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
| |
| va_list args; |
| va_start(args, errorNumber); |
| bool result = tokenStream()->reportStrictWarningErrorNumberVA(pos.begin, errorNumber, args); |
| va_end(args); |
| return result; |
| } |
| |
| bool |
| BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...) |
| { |
| TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
| |
| va_list args; |
| va_start(args, errorNumber); |
| bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(), |
| errorNumber, args); |
| va_end(args); |
| return result; |
| } |
| |
| bool |
| BytecodeEmitter::emitNewInit(JSProtoKey key) |
| { |
| const size_t len = 1 + UINT32_INDEX_LEN; |
| ptrdiff_t offset; |
| if (!emitCheck(len, &offset)) |
| return false; |
| |
| jsbytecode* code = this->code(offset); |
| code[0] = JSOP_NEWINIT; |
| code[1] = jsbytecode(key); |
| code[2] = 0; |
| code[3] = 0; |
| code[4] = 0; |
| updateDepth(offset); |
| checkTypeSet(JSOP_NEWINIT); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::iteratorResultShape(unsigned* shape) |
| { |
| // No need to do any guessing for the object kind, since we know exactly how |
| // many properties we plan to have. |
| gc::AllocKind kind = gc::GetGCObjectKind(2); |
| RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject)); |
| if (!obj) |
| return false; |
| |
| Rooted<jsid> value_id(cx, AtomToId(cx->names().value)); |
| Rooted<jsid> done_id(cx, AtomToId(cx->names().done)); |
| if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr, |
| JSPROP_ENUMERATE)) |
| { |
| return false; |
| } |
| if (!NativeDefineProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr, |
| JSPROP_ENUMERATE)) |
| { |
| return false; |
| } |
| |
| ObjectBox* objbox = parser->newObjectBox(obj); |
| if (!objbox) |
| return false; |
| |
| *shape = objectList.add(objbox); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitPrepareIteratorResult() |
| { |
| unsigned shape; |
| if (!iteratorResultShape(&shape)) |
| return false; |
| return emitIndex32(JSOP_NEWOBJECT, shape); |
| } |
| |
| bool |
| BytecodeEmitter::emitFinishIteratorResult(bool done) |
| { |
| jsatomid value_id; |
| if (!makeAtomIndex(cx->names().value, &value_id)) |
| return false; |
| jsatomid done_id; |
| if (!makeAtomIndex(cx->names().done, &done_id)) |
| return false; |
| |
| if (!emitIndex32(JSOP_INITPROP, value_id)) |
| return false; |
| if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) |
| return false; |
| if (!emitIndex32(JSOP_INITPROP, done_id)) |
| return false; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitNameOp(ParseNode* pn, bool callContext) |
| { |
| if (!bindNameToSlot(pn)) |
| return false; |
| |
| JSOp op = pn->getOp(); |
| |
| if (op == JSOP_CALLEE) { |
| if (!emit1(op)) |
| return false; |
| } else { |
| if (!pn->pn_scopecoord.isFree()) { |
| MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ATOM); |
| if (!emitVarOp(pn, op)) |
| return false; |
| } else { |
| if (!emitAtomOp(pn, op)) |
| return false; |
| } |
| } |
| |
| /* Need to provide |this| value for call */ |
| if (callContext) { |
| if (op == JSOP_GETNAME || op == JSOP_GETGNAME) { |
| JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS; |
| if (!emitAtomOp(pn, thisOp)) |
| return false; |
| } else { |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitPropLHS(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_DOT)); |
| MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper()); |
| |
| ParseNode* pn2 = pn->maybeExpr(); |
| |
| /* |
| * If the object operand is also a dotted property reference, reverse the |
| * list linked via pn_expr temporarily so we can iterate over it from the |
| * bottom up (reversing again as we go), to avoid excessive recursion. |
| */ |
| if (pn2->isKind(PNK_DOT) && !pn2->as<PropertyAccess>().isSuper()) { |
| ParseNode* pndot = pn2; |
| ParseNode* pnup = nullptr; |
| ParseNode* pndown; |
| for (;;) { |
| /* Reverse pndot->pn_expr to point up, not down. */ |
| MOZ_ASSERT(!pndot->isUsed()); |
| pndown = pndot->pn_expr; |
| pndot->pn_expr = pnup; |
| if (!pndown->isKind(PNK_DOT) || pndown->as<PropertyAccess>().isSuper()) |
| break; |
| pnup = pndot; |
| pndot = pndown; |
| } |
| |
| /* pndown is a primary expression, not a dotted property reference. */ |
| if (!emitTree(pndown)) |
| return false; |
| |
| do { |
| /* Walk back up the list, emitting annotated name ops. */ |
| if (!emitAtomOp(pndot, JSOP_GETPROP)) |
| return false; |
| |
| /* Reverse the pn_expr link again. */ |
| pnup = pndot->pn_expr; |
| pndot->pn_expr = pndown; |
| pndown = pndot; |
| } while ((pndot = pnup) != nullptr); |
| return true; |
| } |
| |
| // The non-optimized case. |
| return emitTree(pn2); |
| } |
| |
| bool |
| BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall) |
| { |
| if (!emitGetThisForSuperBase(superBase)) |
| return false; |
| if (isCall && !emit1(JSOP_DUP)) |
| return false; |
| if (!emit1(JSOP_SUPERBASE)) |
| return false; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op) |
| { |
| MOZ_ASSERT(pn->isArity(PN_NAME)); |
| |
| if (!emitPropLHS(pn)) |
| return false; |
| |
| if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) |
| return false; |
| |
| if (!emitAtomOp(pn, op)) |
| return false; |
| |
| if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall) |
| { |
| ParseNode* base = &pn->as<PropertyAccess>().expression(); |
| if (!emitSuperPropLHS(base, isCall)) |
| return false; |
| |
| if (!emitAtomOp(pn, op)) |
| return false; |
| |
| if (isCall && !emit1(JSOP_SWAP)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitPropIncDec(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->pn_kid->isKind(PNK_DOT)); |
| |
| bool post; |
| bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper(); |
| JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
| |
| if (isSuper) { |
| ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression(); |
| if (!emitSuperPropLHS(base)) // THIS OBJ |
| return false; |
| if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ |
| return false; |
| } else { |
| if (!emitPropLHS(pn->pn_kid)) // OBJ |
| return false; |
| if (!emit1(JSOP_DUP)) // OBJ OBJ |
| return false; |
| } |
| if (!emitAtomOp(pn->pn_kid, isSuper? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V |
| return false; |
| if (!emit1(JSOP_POS)) // OBJ N |
| return false; |
| if (post && !emit1(JSOP_DUP)) // OBJ N? N |
| return false; |
| if (!emit1(JSOP_ONE)) // OBJ N? N 1 |
| return false; |
| if (!emit1(binop)) // OBJ N? N+1 |
| return false; |
| |
| if (post) { |
| if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ |
| return false; |
| if (!emit1(JSOP_SWAP)) // N? OBJ N+1 |
| return false; |
| if (isSuper) { |
| if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ |
| return false; |
| if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1 |
| return false; |
| } |
| } |
| |
| JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER |
| : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; |
| if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1 |
| return false; |
| if (post && !emit1(JSOP_POP)) // RESULT |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitNameIncDec(ParseNode* pn) |
| { |
| const JSCodeSpec* cs = &CodeSpec[pn->pn_kid->getOp()]; |
| |
| bool global = (cs->format & JOF_GNAME); |
| bool post; |
| JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
| |
| if (!emitAtomOp(pn->pn_kid, global ? JSOP_BINDGNAME : JSOP_BINDNAME)) // OBJ |
| return false; |
| if (!emitAtomOp(pn->pn_kid, global ? JSOP_GETGNAME : JSOP_GETNAME)) // OBJ V |
| return false; |
| if (!emit1(JSOP_POS)) // OBJ N |
| return false; |
| if (post && !emit1(JSOP_DUP)) // OBJ N? N |
| return false; |
| if (!emit1(JSOP_ONE)) // OBJ N? N 1 |
| return false; |
| if (!emit1(binop)) // OBJ N? N+1 |
| return false; |
| |
| if (post) { |
| if (!emit2(JSOP_PICK, 2)) // N? N+1 OBJ |
| return false; |
| if (!emit1(JSOP_SWAP)) // N? OBJ N+1 |
| return false; |
| } |
| |
| JSOp setOp = strictifySetNameOp(global ? JSOP_SETGNAME : JSOP_SETNAME); |
| if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1 |
| return false; |
| if (post && !emit1(JSOP_POP)) // RESULT |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts) |
| { |
| MOZ_ASSERT(pn->isArity(PN_BINARY)); |
| |
| if (!emitTree(pn->pn_left)) |
| return false; |
| |
| if (opts == EmitElemOption::IncDec) { |
| if (!emit1(JSOP_CHECKOBJCOERCIBLE)) |
| return false; |
| } else if (opts == EmitElemOption::Call) { |
| if (!emit1(JSOP_DUP)) |
| return false; |
| } |
| |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 2)) |
| return false; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_ELEM) && pn->as<PropertyByValue>().isSuper()); |
| |
| // The ordering here is somewhat screwy. We need to evaluate the propval |
| // first, by spec. Do a little dance to not emit more than one JSOP_THIS. |
| // Since JSOP_THIS might throw in derived class constructors, we cannot |
| // just push it earlier as the receiver. We have to swap it down instead. |
| |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| // We need to convert the key to an object id first, so that we do not do |
| // it inside both the GETELEM and the SETELEM. |
| if (opts == EmitElemOption::IncDec && !emit1(JSOP_TOID)) |
| return false; |
| |
| if (!emitGetThisForSuperBase(pn->pn_left)) |
| return false; |
| |
| if (opts == EmitElemOption::Call) { |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| |
| // We need another |this| on top, also |
| if (!emitDupAt(1)) |
| return false; |
| } |
| |
| if (!emit1(JSOP_SUPERBASE)) |
| return false; |
| |
| if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitElemOpBase(JSOp op) |
| { |
| if (!emit1(op)) |
| return false; |
| |
| checkTypeSet(op); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op) |
| { |
| EmitElemOption opts = EmitElemOption::Get; |
| if (op == JSOP_CALLELEM) |
| opts = EmitElemOption::Call; |
| else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM) |
| opts = EmitElemOption::Set; |
| |
| return emitElemOperands(pn, opts) && emitElemOpBase(op); |
| } |
| |
| bool |
| BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) |
| { |
| EmitElemOption opts = EmitElemOption::Get; |
| if (isCall) |
| opts = EmitElemOption::Call; |
| else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER) |
| opts = EmitElemOption::Set; |
| |
| if (!emitSuperElemOperands(pn, opts)) |
| return false; |
| if (!emitElemOpBase(op)) |
| return false; |
| |
| if (isCall && !emit1(JSOP_SWAP)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitElemIncDec(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->pn_kid->isKind(PNK_ELEM)); |
| |
| bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper(); |
| |
| if (isSuper) { |
| if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec)) |
| return false; |
| } else { |
| if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) |
| return false; |
| } |
| |
| bool post; |
| JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
| |
| JSOp getOp; |
| if (isSuper) { |
| // There's no such thing as JSOP_DUP3, so we have to be creative. |
| // Note that pushing things again is no fewer JSOps. |
| if (!emitDupAt(2)) // KEY THIS OBJ KEY |
| return false; |
| if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS |
| return false; |
| if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ |
| return false; |
| getOp = JSOP_GETELEM_SUPER; |
| } else { |
| // We need to convert the key to an object id first, so that we do not do |
| // it inside both the GETELEM and the SETELEM. In the super case, this is |
| // done by emitSuperElemOperands. |
| // OBJ KEY* |
| if (!emit1(JSOP_TOID)) // OBJ KEY |
| return false; |
| if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY |
| return false; |
| getOp = JSOP_GETELEM; |
| } |
| if (!emitElemOpBase(getOp)) // OBJ KEY V |
| return false; |
| if (!emit1(JSOP_POS)) // OBJ KEY N |
| return false; |
| if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N |
| return false; |
| if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1 |
| return false; |
| if (!emit1(binop)) // OBJ KEY N? N+1 |
| return false; |
| |
| if (post) { |
| if (isSuper) { |
| // We have one more value to rotate around, because of |this| |
| // on the stack |
| if (!emit2(JSOP_PICK, 4)) |
| return false; |
| } |
| if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ |
| return false; |
| if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY |
| return false; |
| if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1 |
| return false; |
| } |
| |
| JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) |
| : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); |
| if (!emitElemOpBase(setOp)) // N? N+1 |
| return false; |
| if (post && !emit1(JSOP_POP)) // RESULT |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitNumberOp(double dval) |
| { |
| int32_t ival; |
| if (NumberIsInt32(dval, &ival)) { |
| if (ival == 0) |
| return emit1(JSOP_ZERO); |
| if (ival == 1) |
| return emit1(JSOP_ONE); |
| if ((int)(int8_t)ival == ival) |
| return emit2(JSOP_INT8, uint8_t(int8_t(ival))); |
| |
| uint32_t u = uint32_t(ival); |
| if (u < JS_BIT(16)) { |
| if (!emitUint16Operand(JSOP_UINT16, u)) |
| return false; |
| } else if (u < JS_BIT(24)) { |
| ptrdiff_t off; |
| if (!emitN(JSOP_UINT24, 3, &off)) |
| return false; |
| SET_UINT24(code(off), u); |
| } else { |
| ptrdiff_t off; |
| if (!emitN(JSOP_INT32, 4, &off)) |
| return false; |
| SET_INT32(code(off), ival); |
| } |
| return true; |
| } |
| |
| if (!constList.append(DoubleValue(dval))) |
| return false; |
| |
| return emitIndex32(JSOP_DOUBLE, constList.length() - 1); |
| } |
| |
| void |
| BytecodeEmitter::setJumpOffsetAt(ptrdiff_t off) |
| { |
| SET_JUMP_OFFSET(code(off), offset() - off); |
| } |
| |
| bool |
| BytecodeEmitter::pushInitialConstants(JSOp op, unsigned n) |
| { |
| MOZ_ASSERT(op == JSOP_UNDEFINED || op == JSOP_UNINITIALIZED); |
| |
| for (unsigned i = 0; i < n; ++i) { |
| if (!emit1(op)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::initializeBlockScopedLocalsFromStack(Handle<StaticBlockObject*> blockObj) |
| { |
| for (unsigned i = blockObj->numVariables(); i > 0; --i) { |
| if (blockObj->isAliased(i - 1)) { |
| ScopeCoordinate sc; |
| sc.setHops(0); |
| sc.setSlot(BlockObject::RESERVED_SLOTS + i - 1); |
| if (!emitAliasedVarOp(JSOP_INITALIASEDLEXICAL, sc, DontCheckLexical)) |
| return false; |
| } else { |
| // blockIndexToLocalIndex returns the slot index after the unaliased |
| // locals stored in the frame. EmitUnaliasedVarOp expects the slot index |
| // to include both unaliased and aliased locals, so we have to add the |
| // number of aliased locals. |
| uint32_t numAliased = script->bindings.numAliasedBodyLevelLocals(); |
| unsigned local = blockObj->blockIndexToLocalIndex(i - 1) + numAliased; |
| if (!emitUnaliasedVarOp(JSOP_INITLEXICAL, local, DontCheckLexical)) |
| return false; |
| } |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp, |
| unsigned alreadyPushed) |
| { |
| // This is so terrible. The eval body-level lexical scope needs to be |
| // emitted in the prologue so DEFFUN can pick up the right scope chain. |
| bool isEvalBodyLexicalScope = sc->staticScope()->is<StaticEvalObject>() && |
| !innermostStmt(); |
| if (isEvalBodyLexicalScope) { |
| MOZ_ASSERT(code().length() == 0); |
| switchToPrologue(); |
| } |
| |
| // Initial values for block-scoped locals. Whether it is undefined or the |
| // JS_UNINITIALIZED_LEXICAL magic value depends on the context. The |
| // current way we emit for-in and for-of heads means its let bindings will |
| // always be initialized, so we can initialize them to undefined. |
| Rooted<StaticBlockObject*> blockObj(cx, &objbox->object->as<StaticBlockObject>()); |
| if (!pushInitialConstants(initialValueOp, blockObj->numVariables() - alreadyPushed)) |
| return false; |
| |
| if (!enterNestedScope(stmtInfo, objbox, StmtType::BLOCK)) |
| return false; |
| |
| if (!initializeBlockScopedLocalsFromStack(blockObj)) |
| return false; |
| |
| if (isEvalBodyLexicalScope) |
| switchToMain(); |
| |
| return true; |
| } |
| |
| /* |
| * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. |
| * LLVM is deciding to inline this function which uses a lot of stack space |
| * into emitTree which is recursive and uses relatively little stack space. |
| */ |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitSwitch(ParseNode* pn) |
| { |
| ParseNode* cases = pn->pn_right; |
| MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST)); |
| |
| // Emit code for the discriminant. |
| if (!emitTree(pn->pn_left)) |
| return false; |
| |
| StmtInfoBCE stmtInfo(cx); |
| ptrdiff_t top; |
| if (cases->isKind(PNK_LEXICALSCOPE)) { |
| if (!enterBlockScope(&stmtInfo, cases->pn_objbox, JSOP_UNINITIALIZED, 0)) |
| return false; |
| |
| stmtInfo.type = StmtType::SWITCH; |
| stmtInfo.update = top = offset(); |
| |
| // Advance |cases| to refer to the switch case list. |
| cases = cases->expr(); |
| } else { |
| MOZ_ASSERT(cases->isKind(PNK_STATEMENTLIST)); |
| top = offset(); |
| pushStatement(&stmtInfo, StmtType::SWITCH, top); |
| } |
| |
| // Switch bytecodes run from here till end of final case. |
| uint32_t caseCount = cases->pn_count; |
| if (caseCount > JS_BIT(16)) { |
| parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES); |
| return false; |
| } |
| |
| // Try for most optimal, fall back if not dense ints. |
| JSOp switchOp = JSOP_TABLESWITCH; |
| uint32_t tableLength = 0; |
| int32_t low, high; |
| bool hasDefault = false; |
| CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr; |
| if (caseCount == 0 || |
| (caseCount == 1 && (hasDefault = firstCase->isDefault()))) |
| { |
| caseCount = 0; |
| low = 0; |
| high = -1; |
| } else { |
| Vector<jsbitmap, 128, SystemAllocPolicy> intmap; |
| int32_t intmapBitLength = 0; |
| |
| low = JSVAL_INT_MAX; |
| high = JSVAL_INT_MIN; |
| |
| for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { |
| if (caseNode->isDefault()) { |
| hasDefault = true; |
| caseCount--; // one of the "cases" was the default |
| continue; |
| } |
| |
| if (switchOp == JSOP_CONDSWITCH) |
| continue; |
| |
| MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); |
| |
| ParseNode* caseValue = caseNode->caseExpression(); |
| |
| if (caseValue->getKind() != PNK_NUMBER) { |
| switchOp = JSOP_CONDSWITCH; |
| continue; |
| } |
| |
| int32_t i; |
| if (!NumberIsInt32(caseValue->pn_dval, &i)) { |
| switchOp = JSOP_CONDSWITCH; |
| continue; |
| } |
| |
| if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) { |
| switchOp = JSOP_CONDSWITCH; |
| continue; |
| } |
| if (i < low) |
| low = i; |
| if (i > high) |
| high = i; |
| |
| // Check for duplicates, which require a JSOP_CONDSWITCH. |
| // We bias i by 65536 if it's negative, and hope that's a rare |
| // case (because it requires a malloc'd bitmap). |
| if (i < 0) |
| i += JS_BIT(16); |
| if (i >= intmapBitLength) { |
| size_t newLength = (i / JS_BITMAP_NBITS) + 1; |
| if (!intmap.resize(newLength)) |
| return false; |
| intmapBitLength = newLength * JS_BITMAP_NBITS; |
| } |
| if (JS_TEST_BIT(intmap, i)) { |
| switchOp = JSOP_CONDSWITCH; |
| continue; |
| } |
| JS_SET_BIT(intmap, i); |
| } |
| |
| // Compute table length and select condswitch instead if overlarge or |
| // more than half-sparse. |
| if (switchOp == JSOP_TABLESWITCH) { |
| tableLength = uint32_t(high - low + 1); |
| if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount) |
| switchOp = JSOP_CONDSWITCH; |
| } |
| } |
| |
| // The note has one or two offsets: first tells total switch code length; |
| // second (if condswitch) tells offset to first JSOP_CASE. |
| unsigned noteIndex; |
| size_t switchSize; |
| if (switchOp == JSOP_CONDSWITCH) { |
| // 0 bytes of immediate for unoptimized switch. |
| switchSize = 0; |
| if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex)) |
| return false; |
| } else { |
| MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); |
| |
| // 3 offsets (len, low, high) before the table, 1 per entry. |
| switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength)); |
| if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex)) |
| return false; |
| } |
| |
| // Emit switchOp followed by switchSize bytes of jump or lookup table. |
| if (!emitN(switchOp, switchSize)) |
| return false; |
| |
| Vector<CaseClause*, 32, SystemAllocPolicy> table; |
| |
| ptrdiff_t condSwitchDefaultOff = -1; |
| if (switchOp == JSOP_CONDSWITCH) { |
| unsigned caseNoteIndex; |
| bool beforeCases = true; |
| ptrdiff_t prevCaseOffset; |
| |
| // Emit code for evaluating cases and jumping to case statements. |
| for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { |
| ParseNode* caseValue = caseNode->caseExpression(); |
| |
| // If the expression is a literal, suppress line number emission so |
| // that debugging works more naturally. |
| if (caseValue) { |
| if (!emitTree(caseValue, |
| caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) |
| { |
| return false; |
| } |
| } |
| |
| if (!beforeCases) { |
| // prevCaseOffset is the previous JSOP_CASE's bytecode offset. |
| if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - prevCaseOffset)) |
| return false; |
| } |
| if (!caseValue) { |
| // This is the default clause. |
| continue; |
| } |
| |
| if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex)) |
| return false; |
| if (!emitJump(JSOP_CASE, 0, &prevCaseOffset)) |
| return false; |
| caseNode->setOffset(prevCaseOffset); |
| |
| if (beforeCases) { |
| // Switch note's second offset is to first JSOP_CASE. |
| unsigned noteCount = notes().length(); |
| if (!setSrcNoteOffset(noteIndex, 1, prevCaseOffset - top)) |
| return false; |
| unsigned noteCountDelta = notes().length() - noteCount; |
| if (noteCountDelta != 0) |
| caseNoteIndex += noteCountDelta; |
| beforeCases = false; |
| } |
| } |
| |
| // If we didn't have an explicit default (which could fall in between |
| // cases, preventing us from fusing this setSrcNoteOffset with the call |
| // in the loop above), link the last case to the implicit default for |
| // the benefit of IonBuilder. |
| if (!hasDefault && |
| !beforeCases && |
| !setSrcNoteOffset(caseNoteIndex, 0, offset() - prevCaseOffset)) |
| { |
| return false; |
| } |
| |
| // Emit default even if no explicit default statement. |
| if (!emitJump(JSOP_DEFAULT, 0, &condSwitchDefaultOff)) |
| return false; |
| } else { |
| MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); |
| jsbytecode* pc = code(top + JUMP_OFFSET_LEN); |
| |
| // Fill in switch bounds, which we know fit in 16-bit offsets. |
| SET_JUMP_OFFSET(pc, low); |
| pc += JUMP_OFFSET_LEN; |
| SET_JUMP_OFFSET(pc, high); |
| pc += JUMP_OFFSET_LEN; |
| |
| if (tableLength != 0) { |
| if (!table.growBy(tableLength)) |
| return false; |
| |
| for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { |
| if (ParseNode* caseValue = caseNode->caseExpression()) { |
| MOZ_ASSERT(caseValue->isKind(PNK_NUMBER)); |
| |
| int32_t i = int32_t(caseValue->pn_dval); |
| MOZ_ASSERT(double(i) == caseValue->pn_dval); |
| |
| i -= low; |
| MOZ_ASSERT(uint32_t(i) < tableLength); |
| MOZ_ASSERT(!table[i]); |
| table[i] = caseNode; |
| } |
| } |
| } |
| } |
| |
| ptrdiff_t defaultOffset = -1; |
| |
| // Emit code for each case's statements. |
| for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { |
| if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) |
| setJumpOffsetAt(caseNode->offset()); |
| |
| // If this is emitted as a TABLESWITCH, we'll need to know this case's |
| // offset later when emitting the table. Store it in the node's |
| // pn_offset (giving the field a different meaning vs. how we used it |
| // on the immediately preceding line of code). |
| ptrdiff_t here = offset(); |
| caseNode->setOffset(here); |
| if (caseNode->isDefault()) |
| defaultOffset = here - top; |
| |
| if (!emitTree(caseNode->statementList())) |
| return false; |
| } |
| |
| if (!hasDefault) { |
| // If no default case, offset for default is to end of switch. |
| defaultOffset = offset() - top; |
| } |
| MOZ_ASSERT(defaultOffset != -1); |
| |
| // Set the default offset (to end of switch if no default). |
| jsbytecode* pc; |
| if (switchOp == JSOP_CONDSWITCH) { |
| pc = nullptr; |
| SET_JUMP_OFFSET(code(condSwitchDefaultOff), defaultOffset - (condSwitchDefaultOff - top)); |
| } else { |
| pc = code(top); |
| SET_JUMP_OFFSET(pc, defaultOffset); |
| pc += JUMP_OFFSET_LEN; |
| } |
| |
| // Set the SRC_SWITCH note's offset operand to tell end of switch. |
| if (!setSrcNoteOffset(noteIndex, 0, offset() - top)) |
| return false; |
| |
| if (switchOp == JSOP_TABLESWITCH) { |
| // Skip over the already-initialized switch bounds. |
| pc += 2 * JUMP_OFFSET_LEN; |
| |
| // Fill in the jump table, if there is one. |
| for (uint32_t i = 0; i < tableLength; i++) { |
| CaseClause* caseNode = table[i]; |
| ptrdiff_t off = caseNode ? caseNode->offset() - top : 0; |
| SET_JUMP_OFFSET(pc, off); |
| pc += JUMP_OFFSET_LEN; |
| } |
| } |
| |
| if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { |
| if (!leaveNestedScope(&stmtInfo)) |
| return false; |
| } else { |
| popStatement(); |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::isRunOnceLambda() |
| { |
| // The run once lambda flags set by the parser are approximate, and we look |
| // at properties of the function itself before deciding to emit a function |
| // as a run once lambda. |
| |
| if (!(parent && parent->emittingRunOnceLambda) && |
| (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce())) |
| { |
| return false; |
| } |
| |
| FunctionBox* funbox = sc->asFunctionBox(); |
| return !funbox->argumentsHasLocalBinding() && |
| !funbox->isGenerator() && |
| !funbox->function()->name(); |
| } |
| |
| bool |
| BytecodeEmitter::emitYieldOp(JSOp op) |
| { |
| if (op == JSOP_FINALYIELDRVAL) |
| return emit1(JSOP_FINALYIELDRVAL); |
| |
| MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD); |
| |
| ptrdiff_t off; |
| if (!emitN(op, 3, &off)) |
| return false; |
| |
| uint32_t yieldIndex = yieldOffsetList.length(); |
| if (yieldIndex >= JS_BIT(24)) { |
| reportError(nullptr, JSMSG_TOO_MANY_YIELDS); |
| return false; |
| } |
| |
| SET_UINT24(code(off), yieldIndex); |
| |
| if (!yieldOffsetList.append(offset())) |
| return false; |
| |
| return emit1(JSOP_DEBUGAFTERYIELD); |
| } |
| |
| bool |
| BytecodeEmitter::emitCreateFunctionThis() |
| { |
| // Do nothing if the function doesn't have a this-binding (this happens for |
| // instance if it doesn't use this/eval or if it's an arrow function). |
| if (!sc->asFunctionBox()->hasThisBinding()) |
| return true; |
| |
| switchToPrologue(); |
| |
| if (!emit1(JSOP_FUNCTIONTHIS)) |
| return false; |
| |
| BindingIter bi = Bindings::thisBinding(cx, script); |
| if (!emitStoreToTopScope(bi)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| switchToMain(); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSetThis(ParseNode* pn) |
| { |
| // PNK_SETTHIS is used to update |this| after a super() call in a derived |
| // class constructor. |
| |
| MOZ_ASSERT(pn->isKind(PNK_SETTHIS)); |
| |
| ParseNode* name = pn->pn_left; |
| MOZ_ASSERT(name->isKind(PNK_NAME)); |
| |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| if (!bindNameToSlot(name)) |
| return false; |
| |
| JSOp setOp = name->getOp(); |
| |
| // Handle the eval case. Only accept the strict variant, as eval in a |
| // derived class constructor must be strict. |
| if (setOp == JSOP_STRICTSETNAME) { |
| if (!emitAtomOp(name, JSOP_GETNAME)) |
| return false; |
| if (!emit1(JSOP_CHECKTHISREINIT)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| if (!emitAtomOp(name, JSOP_BINDNAME)) |
| return false; |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| |
| return emitAtomOp(name, setOp); |
| } |
| |
| JSOp getOp; |
| switch (setOp) { |
| case JSOP_SETLOCAL: |
| getOp = JSOP_GETLOCAL; |
| setOp = JSOP_INITLEXICAL; |
| break; |
| case JSOP_SETALIASEDVAR: |
| getOp = JSOP_GETALIASEDVAR; |
| setOp = JSOP_INITALIASEDLEXICAL; |
| break; |
| default: MOZ_CRASH("Unexpected op"); |
| } |
| |
| // First, get the original |this| and throw if we already initialized it. |
| if (!emitVarOp(name, getOp)) |
| return false; |
| if (!emit1(JSOP_CHECKTHISREINIT)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| // Emit the set. |
| return emitVarOp(name, setOp); |
| } |
| |
| static bool |
| IsModuleOnScopeChain(JSObject* obj) |
| { |
| for (StaticScopeIter<NoGC> ssi(obj); !ssi.done(); ssi++) { |
| if (ssi.type() == StaticScopeIter<NoGC>::Module) |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| BytecodeEmitter::emitFunctionScript(ParseNode* body) |
| { |
| if (!updateLocalsToFrameSlots()) |
| return false; |
| |
| /* |
| * IonBuilder has assumptions about what may occur immediately after |
| * script->main (e.g., in the case of destructuring params). Thus, put the |
| * following ops into the range [script->code, script->main). Note: |
| * execution starts from script->code, so this has no semantic effect. |
| */ |
| |
| FunctionBox* funbox = sc->asFunctionBox(); |
| |
| // Link the function and the script to each other, so that StaticScopeIter |
| // may walk the scope chain of currently compiling scripts. |
| JSScript::linkToFunctionFromEmitter(cx, script, funbox); |
| |
| // Determine whether the function is defined inside a module. |
| insideModule = IsModuleOnScopeChain(sc->staticScope()); |
| |
| if (funbox->argumentsHasLocalBinding()) { |
| MOZ_ASSERT(offset() == 0); /* See JSScript::argumentsBytecode. */ |
| switchToPrologue(); |
| if (!emit1(JSOP_ARGUMENTS)) |
| return false; |
| BindingIter bi = Bindings::argumentsBinding(cx, script); |
| if (!emitStoreToTopScope(bi)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| switchToMain(); |
| } |
| |
| if (!emitCreateFunctionThis()) |
| return false; |
| |
| /* |
| * Emit a prologue for run-once scripts which will deoptimize JIT code if |
| * the script ends up running multiple times via foo.caller related |
| * shenanigans. |
| */ |
| bool runOnce = isRunOnceLambda(); |
| if (runOnce) { |
| switchToPrologue(); |
| if (!emit1(JSOP_RUNONCE)) |
| return false; |
| switchToMain(); |
| } |
| |
| if (!emitTree(body)) |
| return false; |
| |
| if (sc->isFunctionBox()) { |
| if (sc->asFunctionBox()->isGenerator()) { |
| // If we fall off the end of a generator, do a final yield. |
| if (sc->asFunctionBox()->isStarGenerator() && !emitPrepareIteratorResult()) |
| return false; |
| |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| |
| if (sc->asFunctionBox()->isStarGenerator() && !emitFinishIteratorResult(true)) |
| return false; |
| |
| if (!emit1(JSOP_SETRVAL)) |
| return false; |
| |
| ScopeCoordinate sc; |
| // We know that .generator is on the top scope chain node, as we are |
| // at the function end. |
| sc.setHops(0); |
| MOZ_ALWAYS_TRUE(lookupAliasedNameSlot(cx->names().dotGenerator, &sc)); |
| if (!emitAliasedVarOp(JSOP_GETALIASEDVAR, sc, DontCheckLexical)) |
| return false; |
| |
| // No need to check for finally blocks, etc as in EmitReturn. |
| if (!emitYieldOp(JSOP_FINALYIELDRVAL)) |
| return false; |
| } else { |
| // Non-generator functions just return |undefined|. The JSOP_RETRVAL |
| // emitted below will do that, except if the script has a finally |
| // block: there can be a non-undefined value in the return value |
| // slot. Make sure the return value is |undefined|. |
| if (hasTryFinally) { |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!emit1(JSOP_SETRVAL)) |
| return false; |
| } |
| } |
| } |
| |
| if (sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor()) { |
| BindingIter bi = Bindings::thisBinding(cx, script); |
| if (!emitLoadFromTopScope(bi)) |
| return false; |
| if (!emit1(JSOP_CHECKRETURN)) |
| return false; |
| } |
| |
| // Always end the script with a JSOP_RETRVAL. Some other parts of the codebase |
| // depend on this opcode, e.g. InterpreterRegs::setToEndOfScript. |
| if (!emit1(JSOP_RETRVAL)) |
| return false; |
| |
| // If all locals are aliased, the frame's block slots won't be used, so we |
| // can set numBlockScoped = 0. This is nice for generators as it ensures |
| // nfixed == 0, so we don't have to initialize any local slots when resuming |
| // a generator. |
| if (sc->allLocalsAliased()) |
| script->bindings.setAllLocalsAliased(); |
| |
| if (!JSScript::fullyInitFromEmitter(cx, script, this)) |
| return false; |
| |
| /* |
| * If this function is only expected to run once, mark the script so that |
| * initializers created within it may be given more precise types. |
| */ |
| if (runOnce) { |
| script->setTreatAsRunOnce(); |
| MOZ_ASSERT(!script->hasRunOnce()); |
| } |
| |
| tellDebuggerAboutCompiledScript(cx); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitModuleScript(ParseNode* body) |
| { |
| insideModule = true; |
| |
| if (!updateLocalsToFrameSlots()) |
| return false; |
| |
| /* |
| * IonBuilder has assumptions about what may occur immediately after |
| * script->main (e.g., in the case of destructuring params). Thus, put the |
| * following ops into the range [script->code, script->main). Note: |
| * execution starts from script->code, so this has no semantic effect. |
| */ |
| |
| ModuleBox* modulebox = sc->asModuleBox(); |
| MOZ_ASSERT(modulebox); |
| |
| // Link the module and the script to each other, so that StaticScopeIter |
| // may walk the scope chain of currently compiling scripts. |
| JSScript::linkToModuleFromEmitter(cx, script, modulebox); |
| |
| if (!emitTree(body)) |
| return false; |
| |
| // Always end the script with a JSOP_RETRVAL. Some other parts of the codebase |
| // depend on this opcode, e.g. InterpreterRegs::setToEndOfScript. |
| if (!emit1(JSOP_RETRVAL)) |
| return false; |
| |
| // If all locals are aliased, the frame's block slots won't be used, so we |
| // can set numBlockScoped = 0. This is nice for generators as it ensures |
| // nfixed == 0, so we don't have to initialize any local slots when resuming |
| // a generator. |
| if (sc->allLocalsAliased()) |
| script->bindings.setAllLocalsAliased(); |
| |
| if (!JSScript::fullyInitFromEmitter(cx, script, this)) |
| return false; |
| |
| /* |
| * Since modules are only run once. Mark the script so that initializers |
| * created within it may be given more precise types. |
| */ |
| script->setTreatAsRunOnce(); |
| MOZ_ASSERT(!script->hasRunOnce()); |
| |
| tellDebuggerAboutCompiledScript(cx); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result) |
| { |
| jsatomid atomIndex; |
| |
| if (!pn->pn_scopecoord.isFree()) { |
| atomIndex = pn->pn_scopecoord.slot(); |
| } else { |
| if (!makeAtomIndex(pn->pn_atom, &atomIndex)) |
| return false; |
| } |
| |
| if (JOF_OPTYPE(pn->getOp()) == JOF_ATOM && |
| (!sc->isFunctionBox() || sc->asFunctionBox()->needsCallObject())) |
| { |
| switchToPrologue(); |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| if (!emitIndexOp(prologueOp, atomIndex)) |
| return false; |
| switchToMain(); |
| } |
| |
| if (result) |
| *result = atomIndex; |
| return true; |
| } |
| |
| template <BytecodeEmitter::DestructuringDeclEmitter EmitName> |
| bool |
| BytecodeEmitter::emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* pattern) |
| { |
| if (pattern->isKind(PNK_ARRAY)) { |
| for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) { |
| if (element->isKind(PNK_ELISION)) |
| continue; |
| ParseNode* target = element; |
| if (element->isKind(PNK_SPREAD)) { |
| MOZ_ASSERT(element->pn_kid->isKind(PNK_NAME)); |
| target = element->pn_kid; |
| } |
| if (target->isKind(PNK_ASSIGN)) |
| target = target->pn_left; |
| if (target->isKind(PNK_NAME)) { |
| if (!EmitName(this, prologueOp, target)) |
| return false; |
| } else { |
| if (!emitDestructuringDeclsWithEmitter<EmitName>(prologueOp, target)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); |
| for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { |
| MOZ_ASSERT(member->isKind(PNK_MUTATEPROTO) || |
| member->isKind(PNK_COLON) || |
| member->isKind(PNK_SHORTHAND)); |
| |
| ParseNode* target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right; |
| |
| if (target->isKind(PNK_ASSIGN)) |
| target = target->pn_left; |
| if (target->isKind(PNK_NAME)) { |
| if (!EmitName(this, prologueOp, target)) |
| return false; |
| } else { |
| if (!emitDestructuringDeclsWithEmitter<EmitName>(prologueOp, target)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static bool |
| EmitDestructuringDecl(BytecodeEmitter* bce, JSOp prologueOp, ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_NAME)); |
| if (!bce->bindNameToSlot(pn)) |
| return false; |
| |
| MOZ_ASSERT(!pn->isOp(JSOP_CALLEE)); |
| return bce->maybeEmitVarDecl(prologueOp, pn, nullptr); |
| } |
| |
| bool |
| BytecodeEmitter::emitDestructuringDecls(JSOp prologueOp, ParseNode* pattern) |
| { |
| return emitDestructuringDeclsWithEmitter<EmitDestructuringDecl>(prologueOp, pattern); |
| } |
| |
| static bool |
| EmitInitializeDestructuringDecl(BytecodeEmitter* bce, JSOp prologueOp, ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_NAME)); |
| MOZ_ASSERT(pn->isBound()); |
| return bce->emitVarOp(pn, pn->getOp()); |
| } |
| |
| bool |
| BytecodeEmitter::emitInitializeDestructuringDecls(JSOp prologueOp, ParseNode* pattern) |
| { |
| return emitDestructuringDeclsWithEmitter<EmitInitializeDestructuringDecl>(prologueOp, pattern); |
| } |
| |
| bool |
| BytecodeEmitter::emitDestructuringLHS(ParseNode* target, VarEmitOption emitOption) |
| { |
| MOZ_ASSERT(emitOption != DefineVars); |
| |
| // Now emit the lvalue opcode sequence. If the lvalue is a nested |
| // destructuring initialiser-form, call ourselves to handle it, then pop |
| // the matched value. Otherwise emit an lvalue bytecode sequence followed |
| // by an assignment op. |
| if (target->isKind(PNK_SPREAD)) |
| target = target->pn_kid; |
| else if (target->isKind(PNK_ASSIGN)) |
| target = target->pn_left; |
| if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) { |
| if (!emitDestructuringOpsHelper(target, emitOption)) |
| return false; |
| if (emitOption == InitializeVars) { |
| // Per its post-condition, emitDestructuringOpsHelper has left the |
| // to-be-destructured value on top of the stack. |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } else if (emitOption == PushInitialValues) { |
| // The lhs is a simple name so the to-be-destructured value is |
| // its initial value and there is nothing to do. |
| MOZ_ASSERT(target->getOp() == JSOP_SETLOCAL || target->getOp() == JSOP_INITLEXICAL); |
| MOZ_ASSERT(target->pn_dflags & PND_BOUND); |
| } else { |
| switch (target->getKind()) { |
| case PNK_NAME: |
| if (!bindNameToSlot(target)) |
| return false; |
| |
| switch (target->getOp()) { |
| case JSOP_SETNAME: |
| case JSOP_STRICTSETNAME: |
| case JSOP_SETGNAME: |
| case JSOP_STRICTSETGNAME: |
| case JSOP_INITGLEXICAL: { |
| // This is like ordinary assignment, but with one difference. |
| // |
| // In `a = b`, we first determine a binding for `a` (using |
| // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then |
| // a JSOP_SETNAME instruction. |
| // |
| // In `[a] = [b]`, per spec, `b` is evaluated first, then we |
| // determine a binding for `a`. Then we need to do assignment-- |
| // but the operands are on the stack in the wrong order for |
| // JSOP_SETPROP, so we have to add a JSOP_SWAP. |
| jsatomid atomIndex; |
| if (!makeAtomIndex(target->pn_atom, &atomIndex)) |
| return false; |
| |
| // INITGLEXICAL always initializes a binding on the global |
| // lexical scope and does not need a BINDGNAME. |
| if (!target->isOp(JSOP_INITGLEXICAL)) { |
| bool global = target->isOp(JSOP_SETGNAME) || target->isOp(JSOP_STRICTSETGNAME); |
| JSOp bindOp = global ? JSOP_BINDGNAME : JSOP_BINDNAME; |
| if (!emitIndex32(bindOp, atomIndex)) |
| return false; |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| } |
| |
| if (!emitIndexOp(target->getOp(), atomIndex)) |
| return false; |
| break; |
| } |
| |
| case JSOP_SETLOCAL: |
| case JSOP_SETARG: |
| case JSOP_SETALIASEDVAR: |
| case JSOP_INITLEXICAL: |
| case JSOP_INITALIASEDLEXICAL: |
| if (!emitVarOp(target, target->getOp())) |
| return false; |
| break; |
| |
| default: |
| MOZ_CRASH("emitDestructuringLHS: bad name op"); |
| } |
| break; |
| |
| case PNK_DOT: |
| { |
| // See the (PNK_NAME, JSOP_SETNAME) case above. |
| // |
| // In `a.x = b`, `a` is evaluated first, then `b`, then a |
| // JSOP_SETPROP instruction. |
| // |
| // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we |
| // need a property set -- but the operands are on the stack in the |
| // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. |
| JSOp setOp; |
| if (target->as<PropertyAccess>().isSuper()) { |
| if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) |
| return false; |
| if (!emit2(JSOP_PICK, 2)) |
| return false; |
| setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; |
| } else { |
| if (!emitTree(target->pn_expr)) |
| return false; |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; |
| } |
| if (!emitAtomOp(target, setOp)) |
| return false; |
| break; |
| } |
| |
| case PNK_ELEM: |
| { |
| // See the comment at `case PNK_DOT:` above. This case, |
| // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP |
| // is emitted by emitElemOperands. |
| if (target->as<PropertyByValue>().isSuper()) { |
| JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; |
| if (!emitSuperElemOp(target, setOp)) |
| return false; |
| } else { |
| JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; |
| if (!emitElemOp(target, setOp)) |
| return false; |
| } |
| break; |
| } |
| |
| case PNK_CALL: |
| MOZ_ASSERT(target->pn_xflags & PNX_SETCALL); |
| if (!emitTree(target)) |
| return false; |
| |
| // Pop the call return value. Below, we pop the RHS too, balancing |
| // the stack --- presumably for the benefit of bytecode |
| // analysis. (The interpreter will never reach these instructions |
| // since we just emitted JSOP_SETCALL, which always throws. It's |
| // possible no analyses actually depend on this either.) |
| if (!emit1(JSOP_POP)) |
| return false; |
| break; |
| |
| default: |
| MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); |
| } |
| |
| // Pop the assigned value. |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitIteratorNext(ParseNode* pn) |
| { |
| MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting, |
| ".next() iteration is prohibited in self-hosted code because it " |
| "can run user-modifiable iteration code"); |
| |
| if (!emit1(JSOP_DUP)) // ... ITER ITER |
| return false; |
| if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // ... ITER NEXT |
| return false; |
| if (!emit1(JSOP_SWAP)) // ... NEXT ITER |
| return false; |
| if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT |
| return false; |
| checkTypeSet(JSOP_CALL); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitDefault(ParseNode* defaultExpr) |
| { |
| if (!emit1(JSOP_DUP)) // VALUE VALUE |
| return false; |
| if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED |
| return false; |
| if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? |
| return false; |
| // Emit source note to enable ion compilation. |
| if (!newSrcNote(SRC_IF)) |
| return false; |
| ptrdiff_t jump; |
| if (!emitJump(JSOP_IFEQ, 0, &jump)) // VALUE |
| return false; |
| if (!emit1(JSOP_POP)) // . |
| return false; |
| if (!emitTree(defaultExpr)) // DEFAULTVALUE |
| return false; |
| setJumpOffsetAt(jump); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitDestructuringOpsArrayHelper(ParseNode* pattern, VarEmitOption emitOption) |
| { |
| MOZ_ASSERT(pattern->isKind(PNK_ARRAY)); |
| MOZ_ASSERT(pattern->isArity(PN_LIST)); |
| MOZ_ASSERT(this->stackDepth != 0); |
| |
| /* |
| * Use an iterator to destructure the RHS, instead of index lookup. |
| * InitializeVars expects us to leave the *original* value on the stack. |
| */ |
| if (emitOption == InitializeVars) { |
| if (!emit1(JSOP_DUP)) // ... OBJ OBJ |
| return false; |
| } |
| if (!emitIterator()) // ... OBJ? ITER |
| return false; |
| bool needToPopIterator = true; |
| |
| for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { |
| /* |
| * Now push the property name currently being matched, which is the |
| * current property name "label" on the left of a colon in the object |
| * initializer. |
| */ |
| ParseNode* pndefault = nullptr; |
| ParseNode* elem = member; |
| if (elem->isKind(PNK_ASSIGN)) { |
| pndefault = elem->pn_right; |
| elem = elem->pn_left; |
| } |
| |
| if (elem->isKind(PNK_SPREAD)) { |
| /* Create a new array with the rest of the iterator */ |
| if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY |
| return false; |
| if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX |
| return false; |
| if (!emitSpread()) // ... OBJ? ARRAY INDEX |
| return false; |
| if (!emit1(JSOP_POP)) // ... OBJ? ARRAY |
| return false; |
| needToPopIterator = false; |
| } else { |
| if (!emit1(JSOP_DUP)) // ... OBJ? ITER ITER |
| return false; |
| if (!emitIteratorNext(pattern)) // ... OBJ? ITER RESULT |
| return false; |
| if (!emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT |
| return false; |
| if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? |
| return false; |
| |
| // Emit (result.done ? undefined : result.value) |
| // This is mostly copied from emitConditionalExpression, except that this code |
| // does not push new values onto the stack. |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_COND, ¬eIndex)) |
| return false; |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, 0, &beq)) |
| return false; |
| |
| if (!emit1(JSOP_POP)) // ... OBJ? ITER |
| return false; |
| if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED |
| return false; |
| |
| /* Jump around else, fixup the branch, emit else, fixup jump. */ |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| setJumpOffsetAt(beq); |
| |
| if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE |
| return false; |
| |
| setJumpOffsetAt(jmp); |
| if (!setSrcNoteOffset(noteIndex, 0, jmp - beq)) |
| return false; |
| } |
| |
| if (pndefault && !emitDefault(pndefault)) |
| return false; |
| |
| // Destructure into the pattern the element contains. |
| ParseNode* subpattern = elem; |
| if (subpattern->isKind(PNK_ELISION)) { |
| // The value destructuring into an elision just gets ignored. |
| if (!emit1(JSOP_POP)) // ... OBJ? ITER |
| return false; |
| continue; |
| } |
| |
| int32_t depthBefore = this->stackDepth; |
| if (!emitDestructuringLHS(subpattern, emitOption)) |
| return false; |
| |
| if (emitOption == PushInitialValues && needToPopIterator) { |
| /* |
| * After '[x,y]' in 'let ([[x,y], z] = o)', the stack is |
| * | to-be-destructured-value | x | y | |
| * The goal is: |
| * | x | y | z | |
| * so emit a pick to produce the intermediate state |
| * | x | y | to-be-destructured-value | |
| * before destructuring z. This gives the loop invariant that |
| * the to-be-destructured-value is always on top of the stack. |
| */ |
| MOZ_ASSERT((this->stackDepth - this->stackDepth) >= -1); |
| uint32_t pickDistance = uint32_t((this->stackDepth + 1) - depthBefore); |
| if (pickDistance > 0) { |
| if (pickDistance > UINT8_MAX) { |
| reportError(subpattern, JSMSG_TOO_MANY_LOCALS); |
| return false; |
| } |
| if (!emit2(JSOP_PICK, (uint8_t)pickDistance)) |
| return false; |
| } |
| } |
| } |
| |
| if (needToPopIterator && !emit1(JSOP_POP)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName) |
| { |
| MOZ_ASSERT(computedPropName->isKind(PNK_COMPUTED_NAME)); |
| return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID); |
| } |
| |
| bool |
| BytecodeEmitter::emitDestructuringOpsObjectHelper(ParseNode* pattern, VarEmitOption emitOption) |
| { |
| MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); |
| MOZ_ASSERT(pattern->isArity(PN_LIST)); |
| |
| MOZ_ASSERT(this->stackDepth > 0); // ... RHS |
| |
| if (!emitRequireObjectCoercible()) // ... RHS |
| return false; |
| |
| for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { |
| // Duplicate the value being destructured to use as a reference base. |
| if (!emit1(JSOP_DUP)) // ... RHS RHS |
| return false; |
| |
| // Now push the property name currently being matched, which is the |
| // current property name "label" on the left of a colon in the object |
| // initialiser. |
| bool needsGetElem = true; |
| |
| ParseNode* subpattern; |
| if (member->isKind(PNK_MUTATEPROTO)) { |
| if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP |
| return false; |
| needsGetElem = false; |
| subpattern = member->pn_kid; |
| } else { |
| MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); |
| |
| ParseNode* key = member->pn_left; |
| if (key->isKind(PNK_NUMBER)) { |
| if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY |
| return false; |
| } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { |
| PropertyName* name = key->pn_atom->asPropertyName(); |
| |
| // The parser already checked for atoms representing indexes and |
| // used PNK_NUMBER instead, but also watch for ids which TI treats |
| // as indexes for simplification of downstream analysis. |
| jsid id = NameToId(name); |
| if (id != IdToTypeId(id)) { |
| if (!emitTree(key)) // ... RHS RHS KEY |
| return false; |
| } else { |
| if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP |
| return false; |
| needsGetElem = false; |
| } |
| } else { |
| if (!emitComputedPropertyName(key)) // ... RHS RHS KEY |
| return false; |
| } |
| |
| subpattern = member->pn_right; |
| } |
| |
| // Get the property value if not done already. |
| if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP |
| return false; |
| |
| if (subpattern->isKind(PNK_ASSIGN)) { |
| if (!emitDefault(subpattern->pn_right)) |
| return false; |
| subpattern = subpattern->pn_left; |
| } |
| |
| // Destructure PROP per this member's subpattern. |
| int32_t depthBefore = this->stackDepth; |
| if (!emitDestructuringLHS(subpattern, emitOption)) |
| return false; |
| |
| // If emitOption is InitializeVars, destructuring initialized each |
| // target in the subpattern's LHS as it went, then popped PROP. We've |
| // correctly returned to the loop-entry stack, and we continue to the |
| // next member. |
| if (emitOption == InitializeVars) // ... RHS |
| continue; |
| |
| MOZ_ASSERT(emitOption == PushInitialValues); |
| |
| // emitDestructuringLHS removed PROP, and it pushed a value per target |
| // name in LHS (for |emitOption == PushInitialValues| only makes sense |
| // when multiple values need to be pushed onto the stack to initialize |
| // a single lexical scope). It also preserved OBJ deep in the stack as |
| // the original object to be destructed into remaining target names in |
| // the LHS object pattern. (We use PushInitialValues *only* as part of |
| // SpiderMonkey's proprietary let block statements, which assign their |
| // targets all in a single go [akin to Scheme's let, and distinct from |
| // let*/letrec].) Thus for: |
| // |
| // let ({arr: [x, y], z} = obj) { ... } |
| // |
| // we have this stack after the above acts upon the [x, y] subpattern: |
| // |
| // ... OBJ x y |
| // |
| // (where of course x = obj.arr[0] and y = obj.arr[1], and []-indexing |
| // is really iteration-indexing). We want to have: |
| // |
| // ... x y OBJ |
| // |
| // so that we can continue, ready to destruct z from OBJ. Pick OBJ out |
| // of the stack, moving it to the top, to accomplish this. |
| MOZ_ASSERT((this->stackDepth - this->stackDepth) >= -1); |
| uint32_t pickDistance = uint32_t((this->stackDepth + 1) - depthBefore); |
| if (pickDistance > 0) { |
| if (pickDistance > UINT8_MAX) { |
| reportError(subpattern, JSMSG_TOO_MANY_LOCALS); |
| return false; |
| } |
| if (!emit2(JSOP_PICK, (uint8_t)pickDistance)) |
| return false; |
| } |
| } |
| |
| if (emitOption == PushInitialValues) { |
| // Per the above loop invariant, the value being destructured into this |
| // object pattern is atop the stack. Pop it to achieve the |
| // post-condition. |
| if (!emit1(JSOP_POP)) // ... <pattern's target name values, seriatim> |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Recursive helper for emitDestructuringOps. |
| * EmitDestructuringOpsHelper assumes the to-be-destructured value has been |
| * pushed on the stack and emits code to destructure each part of a [] or {} |
| * lhs expression. |
| * |
| * If emitOption is InitializeVars, the initial to-be-destructured value is |
| * left untouched on the stack and the overall depth is not changed. |
| * |
| * 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 emitDestructuringLHS) |
| */ |
| bool |
| BytecodeEmitter::emitDestructuringOpsHelper(ParseNode* pattern, VarEmitOption emitOption) |
| { |
| MOZ_ASSERT(emitOption != DefineVars); |
| |
| if (pattern->isKind(PNK_ARRAY)) |
| return emitDestructuringOpsArrayHelper(pattern, emitOption); |
| return emitDestructuringOpsObjectHelper(pattern, emitOption); |
| } |
| |
| bool |
| BytecodeEmitter::emitDestructuringOps(ParseNode* pattern, bool isLet) |
| { |
| /* |
| * Call our recursive helper to emit the destructuring assignments and |
| * related stack manipulations. |
| */ |
| VarEmitOption emitOption = isLet ? PushInitialValues : InitializeVars; |
| return emitDestructuringOpsHelper(pattern, emitOption); |
| } |
| |
| bool |
| BytecodeEmitter::emitTemplateString(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| |
| bool pushedString = false; |
| |
| for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) { |
| bool isString = (pn2->getKind() == PNK_STRING || pn2->getKind() == PNK_TEMPLATE_STRING); |
| |
| // Skip empty strings. These are very common: a template string like |
| // `${a}${b}` has three empty strings and without this optimization |
| // we'd emit four JSOP_ADD operations instead of just one. |
| if (isString && pn2->pn_atom->empty()) |
| continue; |
| |
| if (!isString) { |
| // We update source notes before emitting the expression |
| if (!updateSourceCoordNotes(pn2->pn_pos.begin)) |
| return false; |
| } |
| |
| if (!emitTree(pn2)) |
| return false; |
| |
| if (!isString) { |
| // We need to convert the expression to a string |
| if (!emit1(JSOP_TOSTRING)) |
| return false; |
| } |
| |
| if (pushedString) { |
| // We've pushed two strings onto the stack. Add them together, leaving just one. |
| if (!emit1(JSOP_ADD)) |
| return false; |
| } else { |
| pushedString = true; |
| } |
| } |
| |
| if (!pushedString) { |
| // All strings were empty, this can happen for something like `${""}`. |
| // Just push an empty string. |
| if (!emitAtomOp(cx->names().empty, JSOP_STRING)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitVariables(ParseNode* pn, VarEmitOption emitOption) |
| { |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| |
| ParseNode* next; |
| for (ParseNode* binding = pn->pn_head; binding; binding = next) { |
| if (!updateSourceCoordNotes(binding->pn_pos.begin)) |
| return false; |
| next = binding->pn_next; |
| |
| if (binding->isKind(PNK_ARRAY) || binding->isKind(PNK_OBJECT)) { |
| // Destructuring BindingPattern in a `for` loop head: |
| // for (let [x, y] of pts) ...; |
| // or in a deprecated comprehension: |
| // a = [x*y for (let [x, y] of pts)]; |
| // |
| // (ES6 calls this a ForDeclaration. When emitting code for a plain |
| // LexicalDeclaration, like `let [x, y] = pt;`, binding will be a |
| // PNK_ASSIGN node, not a PNK_ARRAY node. `let [x, y];` without an |
| // initializer is a SyntaxError.) |
| |
| MOZ_ASSERT(pn->pn_count == 1); |
| if (emitOption == DefineVars) { |
| // Emit JSOP_DEFVAR instructions if needed, but not |
| // destructuring ops. Each iteration of the for-loop is |
| // responsible for initializing these variables, so it's |
| // the caller's responsibility. |
| if (!emitDestructuringDecls(pn->getOp(), binding)) |
| return false; |
| } else { |
| // We're emitting destructuring let binding initialization |
| // for a legacy comprehension expression. See |
| // emitForInOrOfVariables. |
| |
| // Lexical bindings cannot be used before they are |
| // initialized. Similar to the JSOP_INITLEXICAL case below. |
| MOZ_ASSERT(emitOption == InitializeVars); |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!emitInitializeDestructuringDecls(pn->getOp(), binding)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } else if (binding->isKind(PNK_ASSIGN)) { |
| /* |
| * A destructuring initialiser assignment preceded by var will |
| * never occur to the left of 'in' in a for-in loop. As with 'for |
| * (var x = i in o)...', this will cause the entire 'var [a, b] = |
| * i' to be hoisted out of the loop. |
| */ |
| MOZ_ASSERT(binding->isOp(JSOP_NOP)); |
| MOZ_ASSERT(emitOption != DefineVars); |
| |
| /* |
| * To allow the front end to rewrite var f = x; as f = x; when a |
| * function f(){} precedes the var, detect simple name assignment |
| * here and initialize the name. |
| */ |
| if (binding->pn_left->isKind(PNK_NAME)) { |
| if (!emitSingleVariable(pn, binding->pn_left, binding->pn_right, emitOption)) |
| return false; |
| } else { |
| ParseNode* initializer = binding->pn_left; |
| if (!emitDestructuringDecls(pn->getOp(), initializer)) |
| return false; |
| |
| if (!emitTree(binding->pn_right)) |
| return false; |
| |
| if (!emitDestructuringOpsHelper(initializer, emitOption)) |
| return false; |
| |
| if (emitOption == InitializeVars) { |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| } else { |
| if (!emitSingleVariable(pn, binding, binding->maybeExpr(), emitOption)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSingleVariable(ParseNode* pn, ParseNode* binding, ParseNode* initializer, |
| VarEmitOption emitOption) |
| { |
| // NB: if this var redeclares an existing binding, then `binding` is linked |
| // on its definition's use-chain and pn_expr has been overlayed with |
| // pn_lexdef. |
| MOZ_ASSERT(binding->isKind(PNK_NAME)); |
| if (!bindNameToSlot(binding)) |
| return false; |
| |
| JSOp op = binding->getOp(); |
| MOZ_ASSERT(op != JSOP_CALLEE); |
| MOZ_ASSERT(!binding->pn_scopecoord.isFree() || !pn->isOp(JSOP_NOP)); |
| |
| jsatomid atomIndex; |
| if (!maybeEmitVarDecl(pn->getOp(), binding, &atomIndex)) |
| return false; |
| |
| if (initializer) { |
| MOZ_ASSERT(emitOption != DefineVars); |
| if (op == JSOP_SETNAME || |
| op == JSOP_STRICTSETNAME || |
| op == JSOP_SETGNAME || |
| op == JSOP_STRICTSETGNAME) |
| { |
| MOZ_ASSERT(emitOption != PushInitialValues); |
| JSOp bindOp; |
| if (op == JSOP_SETNAME || op == JSOP_STRICTSETNAME) |
| bindOp = JSOP_BINDNAME; |
| else |
| bindOp = JSOP_BINDGNAME; |
| if (!emitIndex32(bindOp, atomIndex)) |
| return false; |
| } |
| |
| bool oldEmittingForInit = emittingForInit; |
| emittingForInit = false; |
| if (!emitTree(initializer)) |
| return false; |
| emittingForInit = oldEmittingForInit; |
| } else if (op == JSOP_INITLEXICAL || |
| op == JSOP_INITGLEXICAL || |
| emitOption == PushInitialValues) |
| { |
| // 'let' bindings cannot be used before they are |
| // initialized. JSOP_INITLEXICAL distinguishes the binding site. |
| MOZ_ASSERT(emitOption != DefineVars); |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| } else { |
| // The declaration is like `var x;`. Nothing to do. |
| return true; |
| } |
| |
| // If we are not initializing, nothing to pop. If we are initializing |
| // lets, we must emit the pops. |
| if (emitOption == InitializeVars) { |
| MOZ_ASSERT_IF(binding->isDefn(), initializer == binding->pn_expr); |
| if (!binding->pn_scopecoord.isFree()) { |
| if (!emitVarOp(binding, op)) |
| return false; |
| } else { |
| if (!emitIndexOp(op, atomIndex)) |
| return false; |
| } |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) |
| { |
| /* |
| * Check left operand type and generate specialized code for it. |
| * Specialize to avoid ECMA "reference type" values on the operand |
| * stack, which impose pervasive runtime "GetValue" costs. |
| */ |
| jsatomid atomIndex = (jsatomid) -1; |
| uint8_t offset = 1; |
| |
| switch (lhs->getKind()) { |
| case PNK_NAME: |
| if (!bindNameToSlot(lhs)) |
| return false; |
| if (lhs->pn_scopecoord.isFree()) { |
| if (!makeAtomIndex(lhs->pn_atom, &atomIndex)) |
| return false; |
| |
| JSOp bindOp; |
| if (lhs->isOp(JSOP_SETNAME) || lhs->isOp(JSOP_STRICTSETNAME)) { |
| bindOp = JSOP_BINDNAME; |
| } else if (lhs->isOp(JSOP_SETGNAME) || lhs->isOp(JSOP_STRICTSETGNAME)) { |
| bindOp = JSOP_BINDGNAME; |
| } else { |
| MOZ_ASSERT(lhs->isOp(JSOP_SETINTRINSIC)); |
| break; |
| } |
| if (!emitIndex32(bindOp, atomIndex)) |
| return false; |
| offset++; |
| } |
| break; |
| case PNK_DOT: |
| if (lhs->as<PropertyAccess>().isSuper()) { |
| if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression())) |
| return false; |
| offset += 2; |
| } else { |
| if (!emitTree(lhs->expr())) |
| return false; |
| offset += 1; |
| } |
| if (!makeAtomIndex(lhs->pn_atom, &atomIndex)) |
| return false; |
| break; |
| case PNK_ELEM: |
| MOZ_ASSERT(lhs->isArity(PN_BINARY)); |
| if (lhs->as<PropertyByValue>().isSuper()) { |
| if (!emitSuperElemOperands(lhs)) |
| return false; |
| offset += 3; |
| } else { |
| if (!emitTree(lhs->pn_left)) |
| return false; |
| if (!emitTree(lhs->pn_right)) |
| return false; |
| offset += 2; |
| } |
| break; |
| case PNK_ARRAY: |
| case PNK_OBJECT: |
| break; |
| case PNK_CALL: |
| MOZ_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
| if (!emitTree(lhs)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| break; |
| default: |
| MOZ_ASSERT(0); |
| } |
| |
| if (op != JSOP_NOP) { |
| MOZ_ASSERT(rhs); |
| switch (lhs->getKind()) { |
| case PNK_NAME: |
| if (lhs->isConst() && lhs->isOp(JSOP_CALLEE)) { |
| if (!emit1(JSOP_CALLEE)) |
| return false; |
| } else if (lhs->isConst() && (lhs->isOp(JSOP_GETNAME) || lhs->isOp(JSOP_GETGNAME))) { |
| if (!emitIndex32(lhs->getOp(), atomIndex)) |
| return false; |
| } else if (lhs->isOp(JSOP_SETNAME) || lhs->isOp(JSOP_STRICTSETNAME)) { |
| if (!emit1(JSOP_DUP)) |
| return false; |
| if (!emitIndex32(JSOP_GETXPROP, atomIndex)) |
| return false; |
| } else if (lhs->isOp(JSOP_SETGNAME) || lhs->isOp(JSOP_STRICTSETGNAME)) { |
| MOZ_ASSERT(lhs->pn_scopecoord.isFree()); |
| if (!emitAtomOp(lhs, JSOP_GETGNAME)) |
| return false; |
| } else if (lhs->isOp(JSOP_SETINTRINSIC)) { |
| MOZ_ASSERT(lhs->pn_scopecoord.isFree()); |
| if (!emitAtomOp(lhs, JSOP_GETINTRINSIC)) |
| return false; |
| } else { |
| JSOp op; |
| switch (lhs->getOp()) { |
| case JSOP_SETARG: op = JSOP_GETARG; break; |
| case JSOP_SETLOCAL: op = JSOP_GETLOCAL; break; |
| case JSOP_SETALIASEDVAR: op = JSOP_GETALIASEDVAR; break; |
| default: MOZ_CRASH("Bad op"); |
| } |
| if (!emitVarOp(lhs, op)) |
| return false; |
| } |
| break; |
| case PNK_DOT: { |
| JSOp getOp; |
| if (lhs->as<PropertyAccess>().isSuper()) { |
| if (!emit1(JSOP_DUP2)) |
| return false; |
| getOp = JSOP_GETPROP_SUPER; |
| } else { |
| if (!emit1(JSOP_DUP)) |
| return false; |
| bool isLength = (lhs->pn_atom == cx->names().length); |
| getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP; |
| } |
| if (!emitIndex32(getOp, atomIndex)) |
| return false; |
| break; |
| } |
| case PNK_ELEM: { |
| JSOp elemOp; |
| if (lhs->as<PropertyByValue>().isSuper()) { |
| if (!emitDupAt(2)) |
| return false; |
| if (!emitDupAt(2)) |
| return false; |
| if (!emitDupAt(2)) |
| return false; |
| elemOp = JSOP_GETELEM_SUPER; |
| } else { |
| if (!emit1(JSOP_DUP2)) |
| return false; |
| elemOp = JSOP_GETELEM; |
| } |
| if (!emitElemOpBase(elemOp)) |
| return false; |
| break; |
| } |
| case PNK_CALL: |
| /* |
| * We just emitted a JSOP_SETCALL (which will always throw) and |
| * popped the call's return value. Push a random value to make sure |
| * the stack depth is correct. |
| */ |
| MOZ_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
| if (!emit1(JSOP_NULL)) |
| return false; |
| break; |
| default:; |
| } |
| } |
| |
| /* Now emit the right operand (it may affect the namespace). */ |
| if (rhs) { |
| if (!emitTree(rhs)) |
| return false; |
| } else { |
| /* |
| * The value to assign is the next enumeration value in a for-in or |
| * for-of loop. That value has already been emitted: by JSOP_ITERNEXT |
| * in the for-in case, or via a GETPROP "value" on the result object in |
| * the for-of case. If offset == 1, that slot is already at the top of |
| * the stack. Otherwise, rearrange the stack to put that value on top. |
| */ |
| if (offset != 1 && !emit2(JSOP_PICK, offset - 1)) |
| return false; |
| } |
| |
| /* If += etc., emit the binary operator with a source note. */ |
| if (op != JSOP_NOP) { |
| if (!lhs->isKind(PNK_NAME)) { |
| if (!newSrcNote(SRC_ASSIGNOP)) |
| return false; |
| } |
| if (!emit1(op)) |
| return false; |
| } |
| |
| /* Finally, emit the specialized assignment bytecode. */ |
| switch (lhs->getKind()) { |
| case PNK_NAME: |
| if (lhs->isOp(JSOP_SETARG) || lhs->isOp(JSOP_SETLOCAL) || lhs->isOp(JSOP_SETALIASEDVAR)) { |
| if (!emitVarOp(lhs, lhs->getOp())) |
| return false; |
| } else { |
| if (!emitIndexOp(lhs->getOp(), atomIndex)) |
| return false; |
| } |
| break; |
| case PNK_DOT: |
| { |
| JSOp setOp = lhs->as<PropertyAccess>().isSuper() ? |
| (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) : |
| (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP); |
| if (!emitIndexOp(setOp, atomIndex)) |
| return false; |
| break; |
| } |
| case PNK_CALL: |
| /* Do nothing. The JSOP_SETCALL we emitted will always throw. */ |
| MOZ_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
| break; |
| case PNK_ELEM: |
| { |
| JSOp setOp = lhs->as<PropertyByValue>().isSuper() ? |
| sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER : |
| sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; |
| if (!emit1(setOp)) |
| return false; |
| break; |
| } |
| case PNK_ARRAY: |
| case PNK_OBJECT: |
| if (!emitDestructuringOps(lhs)) |
| return false; |
| break; |
| default: |
| MOZ_ASSERT(0); |
| } |
| return true; |
| } |
| |
| bool |
| ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, |
| Value* compare, size_t ncompare, NewObjectKind newKind) |
| { |
| MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject); |
| |
| switch (getKind()) { |
| case PNK_NUMBER: |
| vp.setNumber(pn_dval); |
| return true; |
| case PNK_TEMPLATE_STRING: |
| case PNK_STRING: |
| vp.setString(pn_atom); |
| return true; |
| case PNK_TRUE: |
| vp.setBoolean(true); |
| return true; |
| case PNK_FALSE: |
| vp.setBoolean(false); |
| return true; |
| case PNK_NULL: |
| vp.setNull(); |
| return true; |
| case PNK_CALLSITEOBJ: |
| case PNK_ARRAY: { |
| unsigned count; |
| ParseNode* pn; |
| |
| if (allowObjects == DontAllowObjects) { |
| vp.setMagic(JS_GENERIC_MAGIC); |
| return true; |
| } |
| |
| ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal; |
| if (allowObjects == ForCopyOnWriteArray) { |
| arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite; |
| allowObjects = DontAllowObjects; |
| } |
| |
| if (getKind() == PNK_CALLSITEOBJ) { |
| count = pn_count - 1; |
| pn = pn_head->pn_next; |
| } else { |
| MOZ_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST)); |
| count = pn_count; |
| pn = pn_head; |
| } |
| |
| AutoValueVector values(cx); |
| if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count)) |
| return false; |
| size_t idx; |
| for (idx = 0; pn; idx++, pn = pn->pn_next) { |
| if (!pn->getConstantValue(cx, allowObjects, values[idx], values.begin(), idx)) |
| return false; |
| if (values[idx].isMagic(JS_GENERIC_MAGIC)) { |
| vp.setMagic(JS_GENERIC_MAGIC); |
| return true; |
| } |
| } |
| MOZ_ASSERT(idx == count); |
| |
| JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), |
| newKind, arrayKind); |
| if (!obj) |
| return false; |
| |
| if (!CombineArrayElementTypes(cx, obj, compare, ncompare)) |
| return false; |
| |
| vp.setObject(*obj); |
| return true; |
| } |
| case PNK_OBJECT: { |
| MOZ_ASSERT(isOp(JSOP_NEWINIT)); |
| MOZ_ASSERT(!(pn_xflags & PNX_NONCONST)); |
| |
| if (allowObjects == DontAllowObjects) { |
| vp.setMagic(JS_GENERIC_MAGIC); |
| return true; |
| } |
| MOZ_ASSERT(allowObjects == AllowObjects); |
| |
| Rooted<IdValueVector> properties(cx, IdValueVector(cx)); |
| |
| RootedValue value(cx), idvalue(cx); |
| for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) { |
| if (!pn->pn_right->getConstantValue(cx, allowObjects, &value)) |
| return false; |
| if (value.isMagic(JS_GENERIC_MAGIC)) { |
| vp.setMagic(JS_GENERIC_MAGIC); |
| return true; |
| } |
| |
| ParseNode* pnid = pn->pn_left; |
| if (pnid->isKind(PNK_NUMBER)) { |
| idvalue = NumberValue(pnid->pn_dval); |
| } else { |
| MOZ_ASSERT(pnid->isKind(PNK_OBJECT_PROPERTY_NAME) || pnid->isKind(PNK_STRING)); |
| MOZ_ASSERT(pnid->pn_atom != cx->names().proto); |
| idvalue = StringValue(pnid->pn_atom); |
| } |
| |
| RootedId id(cx); |
| if (!ValueToId<CanGC>(cx, idvalue, &id)) |
| return false; |
| |
| if (!properties.append(IdValuePair(id, value))) |
| return false; |
| } |
| |
| JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(), properties.length(), |
| newKind); |
| if (!obj) |
| return false; |
| |
| if (!CombinePlainObjectPropertyTypes(cx, obj, compare, ncompare)) |
| return false; |
| |
| vp.setObject(*obj); |
| return true; |
| } |
| default: |
| MOZ_CRASH("Unexpected node"); |
| } |
| return false; |
| } |
| |
| bool |
| BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn) |
| { |
| NewObjectKind newKind = (pn->getKind() == PNK_OBJECT) ? SingletonObject : TenuredObject; |
| |
| RootedValue value(cx); |
| if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind)) |
| return false; |
| |
| MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton()); |
| |
| ObjectBox* objbox = parser->newObjectBox(&value.toObject()); |
| if (!objbox) |
| return false; |
| |
| return emitObjectOp(objbox, JSOP_OBJECT); |
| } |
| |
| bool |
| BytecodeEmitter::emitCallSiteObject(ParseNode* pn) |
| { |
| RootedValue value(cx); |
| if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value)) |
| return false; |
| |
| MOZ_ASSERT(value.isObject()); |
| |
| ObjectBox* objbox1 = parser->newObjectBox(&value.toObject()); |
| if (!objbox1) |
| return false; |
| |
| if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value)) |
| return false; |
| |
| MOZ_ASSERT(value.isObject()); |
| |
| ObjectBox* objbox2 = parser->newObjectBox(&value.toObject()); |
| if (!objbox2) |
| return false; |
| |
| return emitObjectPairOp(objbox1, objbox2, JSOP_CALLSITEOBJ); |
| } |
| |
| /* See the SRC_FOR source note offsetBias comments later in this file. */ |
| JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1); |
| JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1); |
| |
| namespace { |
| |
| class EmitLevelManager |
| { |
| BytecodeEmitter* bce; |
| public: |
| explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; } |
| ~EmitLevelManager() { bce->emitLevel--; } |
| }; |
| |
| } /* anonymous namespace */ |
| |
| bool |
| BytecodeEmitter::emitCatch(ParseNode* pn) |
| { |
| /* |
| * Morph StmtType::BLOCK to StmtType::CATCH, note the block entry code offset, |
| * and save the block object atom. |
| */ |
| StmtInfoBCE* stmt = innermostStmt(); |
| MOZ_ASSERT(stmt->type == StmtType::BLOCK && stmt->isBlockScope); |
| stmt->type = StmtType::CATCH; |
| |
| /* Go up one statement info record to the TRY or FINALLY record. */ |
| stmt = stmt->enclosing; |
| MOZ_ASSERT(stmt->type == StmtType::TRY || stmt->type == StmtType::FINALLY); |
| |
| /* Pick up the pending exception and bind it to the catch variable. */ |
| if (!emit1(JSOP_EXCEPTION)) |
| return false; |
| |
| /* |
| * Dup the exception object if there is a guard for rethrowing to use |
| * it later when rethrowing or in other catches. |
| */ |
| if (pn->pn_kid2 && !emit1(JSOP_DUP)) |
| return false; |
| |
| ParseNode* pn2 = pn->pn_kid1; |
| switch (pn2->getKind()) { |
| case PNK_ARRAY: |
| case PNK_OBJECT: |
| if (!emitDestructuringOps(pn2)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| break; |
| |
| case PNK_NAME: |
| /* Inline and specialize bindNameToSlot for pn2. */ |
| MOZ_ASSERT(!pn2->pn_scopecoord.isFree()); |
| if (!emitVarOp(pn2, JSOP_INITLEXICAL)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| break; |
| |
| default: |
| MOZ_ASSERT(0); |
| } |
| |
| // If there is a guard expression, emit it and arrange to jump to the next |
| // catch block if the guard expression is false. |
| if (pn->pn_kid2) { |
| if (!emitTree(pn->pn_kid2)) |
| return false; |
| |
| // If the guard expression is false, fall through, pop the block scope, |
| // and jump to the next catch block. Otherwise jump over that code and |
| // pop the dupped exception. |
| ptrdiff_t guardCheck; |
| if (!emitJump(JSOP_IFNE, 0, &guardCheck)) |
| return false; |
| |
| { |
| NonLocalExitScope nle(this); |
| |
| // Move exception back to cx->exception to prepare for |
| // the next catch. |
| if (!emit1(JSOP_THROWING)) |
| return false; |
| |
| // Leave the scope for this catch block. |
| if (!nle.prepareForNonLocalJump(stmt)) |
| return false; |
| |
| // Jump to the next handler. The jump target is backpatched by emitTry. |
| ptrdiff_t guardJump; |
| if (!emitJump(JSOP_GOTO, 0, &guardJump)) |
| return false; |
| stmt->guardJump() = guardJump; |
| } |
| |
| // Back to normal control flow. |
| setJumpOffsetAt(guardCheck); |
| |
| // Pop duplicated exception object as we no longer need it. |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| |
| /* Emit the catch body. */ |
| return emitTree(pn->pn_kid3); |
| } |
| |
| // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the |
| // comment on EmitSwitch. |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitTry(ParseNode* pn) |
| { |
| StmtInfoBCE stmtInfo(cx); |
| |
| // Push stmtInfo to track jumps-over-catches and gosubs-to-finally |
| // for later fixup. |
| // |
| // When a finally block is active (StmtType::FINALLY in our parse context), |
| // non-local jumps (including jumps-over-catches) result in a GOSUB |
| // being written into the bytecode stream and fixed-up later (c.f. |
| // emitBackPatchOp and backPatch). |
| // |
| pushStatement(&stmtInfo, pn->pn_kid3 ? StmtType::FINALLY : StmtType::TRY, offset()); |
| |
| // Since an exception can be thrown at any place inside the try block, |
| // we need to restore the stack and the scope chain before we transfer |
| // the control to the exception handler. |
| // |
| // For that we store in a try note associated with the catch or |
| // finally block the stack depth upon the try entry. The interpreter |
| // uses this depth to properly unwind the stack and the scope chain. |
| // |
| int depth = stackDepth; |
| |
| // Record the try location, then emit the try block. |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_TRY, ¬eIndex)) |
| return false; |
| if (!emit1(JSOP_TRY)) |
| return false; |
| |
| ptrdiff_t tryStart = offset(); |
| if (!emitTree(pn->pn_kid1)) |
| return false; |
| MOZ_ASSERT(depth == stackDepth); |
| |
| // GOSUB to finally, if present. |
| if (pn->pn_kid3) { |
| if (!emitBackPatchOp(&stmtInfo.gosubs())) |
| return false; |
| } |
| |
| // Source note points to the jump at the end of the try block. |
| if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) |
| return false; |
| |
| // Emit jump over catch and/or finally. |
| ptrdiff_t catchJump = -1; |
| if (!emitBackPatchOp(&catchJump)) |
| return false; |
| |
| ptrdiff_t tryEnd = offset(); |
| |
| // If this try has a catch block, emit it. |
| ParseNode* catchList = pn->pn_kid2; |
| if (catchList) { |
| MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); |
| |
| // The emitted code for a catch block looks like: |
| // |
| // [pushblockscope] only if any local aliased |
| // exception |
| // if there is a catchguard: |
| // dup |
| // setlocal 0; pop assign or possibly destructure exception |
| // if there is a catchguard: |
| // < catchguard code > |
| // ifne POST |
| // debugleaveblock |
| // [popblockscope] only if any local aliased |
| // throwing pop exception to cx->exception |
| // goto <next catch block> |
| // POST: pop |
| // < catch block contents > |
| // debugleaveblock |
| // [popblockscope] only if any local aliased |
| // goto <end of catch blocks> non-local; finally applies |
| // |
| // If there's no catch block without a catchguard, the last <next catch |
| // block> points to rethrow code. This code will [gosub] to the finally |
| // code if appropriate, and is also used for the catch-all trynote for |
| // capturing exceptions thrown from catch{} blocks. |
| // |
| for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { |
| MOZ_ASSERT(this->stackDepth == depth); |
| |
| // Clear the frame's return value that might have been set by the |
| // try block: |
| // |
| // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!emit1(JSOP_SETRVAL)) |
| return false; |
| |
| // Emit the lexical scope and catch body. |
| MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); |
| if (!emitTree(pn3)) |
| return false; |
| |
| // gosub <finally>, if required. |
| if (pn->pn_kid3) { |
| if (!emitBackPatchOp(&stmtInfo.gosubs())) |
| return false; |
| MOZ_ASSERT(this->stackDepth == depth); |
| } |
| |
| // Jump over the remaining catch blocks. This will get fixed |
| // up to jump to after catch/finally. |
| if (!emitBackPatchOp(&catchJump)) |
| return false; |
| |
| // If this catch block had a guard clause, patch the guard jump to |
| // come here. |
| if (stmtInfo.guardJump() != -1) { |
| setJumpOffsetAt(stmtInfo.guardJump()); |
| stmtInfo.guardJump() = -1; |
| |
| // If this catch block is the last one, rethrow, delegating |
| // execution of any finally block to the exception handler. |
| if (!pn3->pn_next) { |
| if (!emit1(JSOP_EXCEPTION)) |
| return false; |
| if (!emit1(JSOP_THROW)) |
| return false; |
| } |
| } |
| } |
| } |
| |
| MOZ_ASSERT(this->stackDepth == depth); |
| |
| // Emit the finally handler, if there is one. |
| ptrdiff_t finallyStart = 0; |
| if (pn->pn_kid3) { |
| // Fix up the gosubs that might have been emitted before non-local |
| // jumps to the finally code. |
| backPatch(stmtInfo.gosubs(), code().end(), JSOP_GOSUB); |
| |
| finallyStart = offset(); |
| |
| // Indicate that we're emitting a subroutine body. |
| stmtInfo.type = StmtType::SUBROUTINE; |
| if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) |
| return false; |
| if (!emit1(JSOP_FINALLY)) |
| return false; |
| if (!emit1(JSOP_GETRVAL)) |
| return false; |
| |
| // Clear the frame's return value to make break/continue return |
| // correct value even if there's no other statement before them: |
| // |
| // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!emit1(JSOP_SETRVAL)) |
| return false; |
| |
| if (!emitTree(pn->pn_kid3)) |
| return false; |
| if (!emit1(JSOP_SETRVAL)) |
| return false; |
| if (!emit1(JSOP_RETSUB)) |
| return false; |
| hasTryFinally = true; |
| MOZ_ASSERT(this->stackDepth == depth); |
| } |
| popStatement(); |
| |
| // ReconstructPCStack needs a NOP here to mark the end of the last catch block. |
| if (!emit1(JSOP_NOP)) |
| return false; |
| |
| // Fix up the end-of-try/catch jumps to come here. |
| backPatch(catchJump, code().end(), JSOP_GOTO); |
| |
| // Add the try note last, to let post-order give us the right ordering |
| // (first to last for a given nesting level, inner to outer by level). |
| if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd)) |
| return false; |
| |
| // If we've got a finally, mark try+catch region with additional |
| // trynote to catch exceptions (re)thrown from a catch block or |
| // for the try{}finally{} case. |
| if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitIf(ParseNode* pn) |
| { |
| StmtInfoBCE stmtInfo(cx); |
| |
| /* Initialize so we can detect else-if chains and avoid recursion. */ |
| stmtInfo.type = StmtType::IF; |
| ptrdiff_t beq = -1; |
| ptrdiff_t jmp = -1; |
| unsigned noteIndex = -1; |
| |
| if_again: |
| /* Emit code for the condition before pushing stmtInfo. */ |
| if (!emitTree(pn->pn_kid1)) |
| return false; |
| ptrdiff_t top = offset(); |
| if (stmtInfo.type == StmtType::IF) { |
| pushStatement(&stmtInfo, StmtType::IF, top); |
| } else { |
| /* |
| * We came here from the goto further below that detects else-if |
| * chains, so we must mutate stmtInfo back into a StmtType::IF record. |
| * Also we need a note offset for SRC_IF_ELSE to help IonMonkey. |
| */ |
| MOZ_ASSERT(stmtInfo.type == StmtType::ELSE); |
| stmtInfo.type = StmtType::IF; |
| stmtInfo.update = top; |
| if (!setSrcNoteOffset(noteIndex, 0, jmp - beq)) |
| return false; |
| } |
| |
| /* Emit an annotated branch-if-false around the then part. */ |
| ParseNode* pn3 = pn->pn_kid3; |
| if (!newSrcNote(pn3 ? SRC_IF_ELSE : SRC_IF, ¬eIndex)) |
| return false; |
| if (!emitJump(JSOP_IFEQ, 0, &beq)) |
| return false; |
| |
| /* Emit code for the then and optional else parts. */ |
| if (!emitTree(pn->pn_kid2)) |
| return false; |
| if (pn3) { |
| /* Modify stmtInfo so we know we're in the else part. */ |
| stmtInfo.type = StmtType::ELSE; |
| |
| /* |
| * Emit a JSOP_BACKPATCH op to jump from the end of our then part |
| * around the else part. The popStatement call at the bottom of |
| * this function will fix up the backpatch chain linked from |
| * stmtInfo.breaks. |
| */ |
| if (!emitGoto(&stmtInfo, &stmtInfo.breaks)) |
| return false; |
| jmp = stmtInfo.breaks; |
| |
| /* Ensure the branch-if-false comes here, then emit the else. */ |
| setJumpOffsetAt(beq); |
| if (pn3->isKind(PNK_IF)) { |
| pn = pn3; |
| goto if_again; |
| } |
| |
| if (!emitTree(pn3)) |
| return false; |
| |
| /* |
| * Annotate SRC_IF_ELSE with the offset from branch to jump, for |
| * IonMonkey's benefit. We can't just "back up" from the pc |
| * of the else clause, because we don't know whether an extended |
| * jump was required to leap from the end of the then clause over |
| * the else clause. |
| */ |
| if (!setSrcNoteOffset(noteIndex, 0, jmp - beq)) |
| return false; |
| } else { |
| /* No else part, fixup the branch-if-false to come here. */ |
| setJumpOffsetAt(beq); |
| } |
| |
| popStatement(); |
| return true; |
| } |
| |
| /* |
| * pnLet represents a let-statement: let (x = y) { ... } |
| * |
| */ |
| /* |
| * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
| * the comment on emitSwitch. |
| */ |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitLetBlock(ParseNode* pnLet) |
| { |
| MOZ_ASSERT(pnLet->isArity(PN_BINARY)); |
| ParseNode* varList = pnLet->pn_left; |
| MOZ_ASSERT(varList->isArity(PN_LIST)); |
| ParseNode* letBody = pnLet->pn_right; |
| MOZ_ASSERT(letBody->isLexical() && letBody->isKind(PNK_LEXICALSCOPE)); |
| |
| int letHeadDepth = this->stackDepth; |
| |
| if (!emitVariables(varList, PushInitialValues)) |
| return false; |
| |
| /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ |
| uint32_t valuesPushed = this->stackDepth - letHeadDepth; |
| StmtInfoBCE stmtInfo(cx); |
| if (!enterBlockScope(&stmtInfo, letBody->pn_objbox, JSOP_UNINITIALIZED, valuesPushed)) |
| return false; |
| |
| if (!emitTree(letBody->pn_expr)) |
| return false; |
| |
| if (!leaveNestedScope(&stmtInfo)) |
| return false; |
| |
| return true; |
| } |
| |
| // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
| // the comment on emitSwitch. |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitLexicalScope(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); |
| |
| StmtInfoBCE stmtInfo(cx); |
| if (!enterBlockScope(&stmtInfo, pn->pn_objbox, JSOP_UNINITIALIZED, 0)) |
| return false; |
| |
| if (!emitTree(pn->pn_expr)) |
| return false; |
| |
| if (!leaveNestedScope(&stmtInfo)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitWith(ParseNode* pn) |
| { |
| StmtInfoBCE stmtInfo(cx); |
| if (!emitTree(pn->pn_left)) |
| return false; |
| if (!enterNestedScope(&stmtInfo, pn->pn_binary_obj, StmtType::WITH)) |
| return false; |
| if (!emitTree(pn->pn_right)) |
| return false; |
| if (!leaveNestedScope(&stmtInfo)) |
| return false; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitRequireObjectCoercible() |
| { |
| // For simplicity, handle this in self-hosted code, at cost of 13 bytes of |
| // bytecode versus 1 byte for a dedicated opcode. As more places need this |
| // behavior, we may want to reconsider this tradeoff. |
| |
| #ifdef DEBUG |
| auto depth = this->stackDepth; |
| #endif |
| MOZ_ASSERT(depth > 0); // VAL |
| if (!emit1(JSOP_DUP)) // VAL VAL |
| return false; |
| |
| // Note that "intrinsic" is a misnomer: we're calling a *self-hosted* |
| // function that's not an intrinsic! But it nonetheless works as desired. |
| if (!emitAtomOp(cx->names().RequireObjectCoercible, |
| JSOP_GETINTRINSIC)) // VAL VAL REQUIREOBJECTCOERCIBLE |
| { |
| return false; |
| } |
| if (!emit1(JSOP_UNDEFINED)) // VAL VAL REQUIREOBJECTCOERCIBLE UNDEFINED |
| return false; |
| if (!emit2(JSOP_PICK, 2)) // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL |
| return false; |
| if (!emitCall(JSOP_CALL, 1)) // VAL IGNORED |
| return false; |
| checkTypeSet(JSOP_CALL); |
| |
| if (!emit1(JSOP_POP)) // VAL |
| return false; |
| |
| MOZ_ASSERT(depth == this->stackDepth); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitIterator() |
| { |
| // Convert iterable to iterator. |
| if (!emit1(JSOP_DUP)) // OBJ OBJ |
| return false; |
| if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR |
| return false; |
| if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN |
| return false; |
| if (!emit1(JSOP_SWAP)) // ITERFN OBJ |
| return false; |
| if (!emitCall(JSOP_CALLITER, 0)) // ITER |
| return false; |
| checkTypeSet(JSOP_CALLITER); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_VAR) || pn->isKind(PNK_LET)); |
| |
| // ES6 specifies that loop variables get a fresh binding in each iteration. |
| // This is currently implemented for C-style for(;;) loops, but not |
| // for-in/of loops, though a similar approach should work. See bug 449811. |
| // |
| // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a |
| // scope containing an uninitialized `x`. If EXPR accesses `x`, we should |
| // get a ReferenceError due to the TDZ violation. This is not yet |
| // implemented. See bug 1069480. |
| // |
| // If the left part is 'var x', emit code to define x if necessary using a |
| // prologue opcode, but do not emit a pop. If it's 'let x', we initialize |
| // the lets to not trigger dead zone checks, via InitializeVars. (The |
| // frontend currently assumes use of a 'let', dominated by its declaration, |
| // needs no TDZ check, so we can't just fix the TDZ bug above by not |
| // initializing here.) |
| emittingForInit = true; |
| if (pn->isKind(PNK_VAR)) { |
| if (!emitVariables(pn, DefineVars)) |
| return false; |
| } else { |
| MOZ_ASSERT(pn->isKind(PNK_LET)); |
| if (!emitVariables(pn, InitializeVars)) |
| return false; |
| } |
| emittingForInit = false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn) |
| { |
| MOZ_ASSERT(type == StmtType::FOR_OF_LOOP || type == StmtType::SPREAD); |
| #ifdef DEBUG |
| if (type == StmtType::FOR_OF_LOOP) { |
| MOZ_ASSERT(pn); |
| MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF)); |
| } else { |
| MOZ_ASSERT(!pn); |
| } |
| #endif |
| |
| ptrdiff_t top = offset(); |
| ParseNode* forHead = pn ? pn->pn_left : nullptr; |
| ParseNode* forHeadExpr = forHead ? forHead->pn_kid3 : nullptr; |
| ParseNode* forBody = pn ? pn->pn_right : nullptr; |
| |
| ParseNode* loopDecl = forHead ? forHead->pn_kid1 : nullptr; |
| if (loopDecl && !emitForInOrOfVariables(loopDecl)) |
| return false; |
| |
| if (type == StmtType::FOR_OF_LOOP) { |
| // For-of loops run with two values on the stack: the iterator and the |
| // current result object. |
| |
| // Compile the object expression to the right of 'of'. |
| if (!emitTree(forHeadExpr)) |
| return false; |
| if (!emitIterator()) |
| return false; |
| |
| // Push a dummy result so that we properly enter iteration midstream. |
| if (!emit1(JSOP_UNDEFINED)) // ITER RESULT |
| return false; |
| } |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, type, top); |
| |
| // Jump down to the loop condition to minimize overhead assuming at least |
| // one iteration, as the other loop forms do. Annotate so IonMonkey can |
| // find the loop-closing jump. |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) |
| return false; |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| |
| top = offset(); |
| stmtInfo.setTop(top); |
| if (!emitLoopHead(nullptr)) |
| return false; |
| |
| if (type == StmtType::SPREAD) |
| this->stackDepth++; |
| |
| #ifdef DEBUG |
| int loopDepth = this->stackDepth; |
| #endif |
| |
| // Emit code to assign result.value to the iteration variable. |
| if (type == StmtType::FOR_OF_LOOP) { |
| if (!emit1(JSOP_DUP)) // ITER RESULT RESULT |
| return false; |
| } |
| if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... RESULT VALUE |
| return false; |
| if (type == StmtType::FOR_OF_LOOP) { |
| if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) // ITER RESULT VALUE |
| return false; |
| if (!emit1(JSOP_POP)) // ITER RESULT |
| return false; |
| |
| // The stack should be balanced around the assignment opcode sequence. |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| // Emit code for the loop body. |
| if (!emitTree(forBody)) |
| return false; |
| |
| // Set loop and enclosing "update" offsets, for continue. |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = offset(); |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| } else { |
| if (!emit1(JSOP_INITELEM_INC)) // ITER ARR (I+1) |
| return false; |
| |
| MOZ_ASSERT(this->stackDepth == loopDepth - 1); |
| |
| // StmtType::SPREAD never contain continue, so do not set "update" offset. |
| } |
| |
| // COME FROM the beginning of the loop to here. |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(forHeadExpr)) |
| return false; |
| |
| if (type == StmtType::FOR_OF_LOOP) { |
| if (!emit1(JSOP_POP)) // ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // ITER ITER |
| return false; |
| } else { |
| if (!emitDupAt(2)) // ITER ARR I ITER |
| return false; |
| } |
| if (!emitIteratorNext(forHead)) // ... RESULT |
| return false; |
| if (!emit1(JSOP_DUP)) // ... RESULT RESULT |
| return false; |
| if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... RESULT DONE? |
| return false; |
| |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, top - offset(), &beq)) // ... RESULT |
| return false; |
| |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| // Let Ion know where the closing jump of this loop is. |
| if (!setSrcNoteOffset(noteIndex, 0, beq - jmp)) |
| return false; |
| |
| // Fixup breaks and continues. |
| // For StmtType::SPREAD, just pop innermostStmt(). |
| popStatement(); |
| |
| if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top, offset())) |
| return false; |
| |
| if (type == StmtType::SPREAD) { |
| if (!emit2(JSOP_PICK, 3)) // ARR I RESULT ITER |
| return false; |
| } |
| |
| // Pop the result and the iter. |
| return emitUint16Operand(JSOP_POPN, 2); |
| } |
| |
| bool |
| BytecodeEmitter::emitForIn(ParseNode* pn) |
| { |
| ptrdiff_t top = offset(); |
| ParseNode* forHead = pn->pn_left; |
| ParseNode* forBody = pn->pn_right; |
| |
| ParseNode* loopDecl = forHead->pn_kid1; |
| if (loopDecl && !emitForInOrOfVariables(loopDecl)) |
| return false; |
| |
| /* Compile the object expression to the right of 'in'. */ |
| if (!emitTree(forHead->pn_kid3)) |
| return false; |
| |
| /* |
| * Emit a bytecode to convert top of stack value to the iterator |
| * object depending on the loop variant (for-in, for-each-in, or |
| * destructuring for-in). |
| */ |
| MOZ_ASSERT(pn->isOp(JSOP_ITER)); |
| if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags)) |
| return false; |
| |
| // For-in loops have both the iterator and the value on the stack. Push |
| // undefined to balance the stack. |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::FOR_IN_LOOP, top); |
| |
| /* Annotate so IonMonkey can find the loop-closing jump. */ |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) |
| return false; |
| |
| /* |
| * Jump down to the loop condition to minimize overhead assuming at |
| * least one iteration, as the other loop forms do. |
| */ |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| |
| top = offset(); |
| stmtInfo.setTop(top); |
| if (!emitLoopHead(nullptr)) |
| return false; |
| |
| #ifdef DEBUG |
| int loopDepth = this->stackDepth; |
| #endif |
| |
| // Emit code to assign the enumeration value to the left hand side, but |
| // also leave it on the stack. |
| if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) |
| return false; |
| |
| /* The stack should be balanced around the assignment opcode sequence. */ |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| /* Emit code for the loop body. */ |
| if (!emitTree(forBody)) |
| return false; |
| |
| /* Set loop and enclosing "update" offsets, for continue. */ |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = offset(); |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| |
| /* |
| * Fixup the goto that starts the loop to jump down to JSOP_MOREITER. |
| */ |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(nullptr)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| if (!emit1(JSOP_MOREITER)) |
| return false; |
| if (!emit1(JSOP_ISNOITER)) |
| return false; |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, top - offset(), &beq)) |
| return false; |
| |
| /* Set the srcnote offset so we can find the closing jump. */ |
| if (!setSrcNoteOffset(noteIndex, 0, beq - jmp)) |
| return false; |
| |
| // Fix up breaks and continues. |
| popStatement(); |
| |
| // Pop the enumeration value. |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top, offset())) |
| return false; |
| if (!emit1(JSOP_ENDITER)) |
| return false; |
| |
| return true; |
| } |
| |
| /* C-style `for (init; cond; update) ...` loop. */ |
| bool |
| BytecodeEmitter::emitCStyleFor(ParseNode* pn) |
| { |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, offset()); |
| |
| ParseNode* forHead = pn->pn_left; |
| ParseNode* forBody = pn->pn_right; |
| |
| // If the head of this for-loop declared any lexical variables, the parser |
| // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the |
| // implicit scope of those variables. By the time we get here, we have |
| // already entered that scope. So far, so good. |
| // |
| // ### Scope freshening |
| // |
| // Each iteration of a `for (let V...)` loop creates a fresh loop variable |
| // binding for V, even if the loop is a C-style `for(;;)` loop: |
| // |
| // var funcs = []; |
| // for (let i = 0; i < 2; i++) |
| // funcs.push(function() { return i; }); |
| // assertEq(funcs[0](), 0); // the two closures capture... |
| // assertEq(funcs[1](), 1); // ...two different `i` bindings |
| // |
| // This is implemented by "freshening" the implicit block -- changing the |
| // scope chain to a fresh clone of the instantaneous block object -- each |
| // iteration, just before evaluating the "update" in for(;;) loops. |
| // |
| // No freshening occurs in `for (const ...;;)` as there's no point: you |
| // can't reassign consts. This is observable through the Debugger API. (The |
| // ES6 spec also skips cloning the environment in this case.) |
| bool forLoopRequiresFreshening = false; |
| if (ParseNode* init = forHead->pn_kid1) { |
| forLoopRequiresFreshening = init->isKind(PNK_LET); |
| |
| // Emit the `init` clause, whether it's an expression or a variable |
| // declaration. (The loop variables were hoisted into an enclosing |
| // scope, but we still need to emit code for the initializers.) |
| emittingForInit = true; |
| if (!updateSourceCoordNotes(init->pn_pos.begin)) |
| return false; |
| if (!emitTree(init)) |
| return false; |
| emittingForInit = false; |
| |
| if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { |
| // 'init' is an expression, not a declaration. emitTree left its |
| // value on the stack. |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| |
| /* |
| * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH). |
| * Use tmp to hold the biased srcnote "top" offset, which differs |
| * from the top local variable by the length of the JSOP_GOTO |
| * emitted in between tmp and top if this loop has a condition. |
| */ |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_FOR, ¬eIndex)) |
| return false; |
| if (!emit1(JSOP_NOP)) |
| return false; |
| ptrdiff_t tmp = offset(); |
| |
| ptrdiff_t jmp = -1; |
| if (forHead->pn_kid2) { |
| /* Goto the loop condition, which branches back to iterate. */ |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| } |
| |
| ptrdiff_t top = offset(); |
| stmtInfo.setTop(top); |
| |
| /* Emit code for the loop body. */ |
| if (!emitLoopHead(forBody)) |
| return false; |
| if (jmp == -1 && !emitLoopEntry(forBody)) |
| return false; |
| if (!emitTree(forBody)) |
| return false; |
| |
| /* Set the second note offset so we can find the update part. */ |
| ptrdiff_t tmp2 = offset(); |
| |
| // Set loop and enclosing "update" offsets, for continue. Note that we |
| // continue to immediately *before* the block-freshening: continuing must |
| // refresh the block. |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = offset(); |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| |
| // Freshen the block on the scope chain to expose distinct bindings for each loop |
| // iteration. |
| if (forLoopRequiresFreshening) { |
| // The scope chain only includes an actual block *if* the scope object |
| // is captured and therefore requires cloning. Get the static block |
| // object from the enclosing let-block statement (which *must* be the |
| // let-statement for the guarding condition to have held) and freshen |
| // if the block object needs cloning. |
| StmtInfoBCE* enclosing = stmtInfo.enclosing; |
| MOZ_ASSERT(enclosing->type == StmtType::BLOCK); |
| MOZ_ASSERT(enclosing->isBlockScope); |
| |
| if (enclosing->staticScope->as<StaticBlockObject>().needsClone()) { |
| if (!emit1(JSOP_FRESHENBLOCKSCOPE)) |
| return false; |
| } |
| } |
| |
| /* Check for update code to do before the condition (if any). */ |
| if (ParseNode* update = forHead->pn_kid3) { |
| if (!updateSourceCoordNotes(update->pn_pos.begin)) |
| return false; |
| if (!emitTree(update)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| /* Restore the absolute line number for source note readers. */ |
| uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end); |
| if (currentLine() != lineNum) { |
| if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum))) |
| return false; |
| current->currentLine = lineNum; |
| current->lastColumn = 0; |
| } |
| } |
| |
| ptrdiff_t tmp3 = offset(); |
| |
| if (forHead->pn_kid2) { |
| /* Fix up the goto from top to target the loop condition. */ |
| MOZ_ASSERT(jmp >= 0); |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(forHead->pn_kid2)) |
| return false; |
| |
| if (!emitTree(forHead->pn_kid2)) |
| return false; |
| } else if (!forHead->pn_kid3) { |
| // If there is no condition clause and no update clause, mark |
| // the loop-ending "goto" with the location of the "for". |
| // This ensures that the debugger will stop on each loop |
| // iteration. |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| } |
| |
| /* Set the first note offset so we can find the loop condition. */ |
| if (!setSrcNoteOffset(noteIndex, 0, tmp3 - tmp)) |
| return false; |
| if (!setSrcNoteOffset(noteIndex, 1, tmp2 - tmp)) |
| return false; |
| |
| /* The third note offset helps us find the loop-closing jump. */ |
| if (!setSrcNoteOffset(noteIndex, 2, offset() - tmp)) |
| return false; |
| |
| /* If no loop condition, just emit a loop-closing jump. */ |
| if (!emitJump(forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO, top - offset())) |
| return false; |
| |
| if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top, offset())) |
| return false; |
| |
| /* Now fixup all breaks and continues. */ |
| popStatement(); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitFor(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_FOR)); |
| |
| if (pn->pn_left->isKind(PNK_FORHEAD)) |
| return emitCStyleFor(pn); |
| |
| if (!updateLineNumberNotes(pn->pn_pos.begin)) |
| return false; |
| |
| if (pn->pn_left->isKind(PNK_FORIN)) |
| return emitForIn(pn); |
| |
| MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF)); |
| return emitForOf(StmtType::FOR_OF_LOOP, pn); |
| } |
| |
| bool |
| BytecodeEmitter::emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letBlockScope) |
| { |
| // ES6 specifies that lexical for-loop variables get a fresh binding each |
| // iteration, and that evaluation of the expression looped over occurs with |
| // these variables uninitialized. But these rules only apply to *standard* |
| // for-in/of loops, and we haven't extended these requirements to |
| // comprehension syntax. |
| |
| *letBlockScope = pn->isKind(PNK_LEXICALSCOPE); |
| if (*letBlockScope) { |
| // This is initially-ES7-tracked syntax, now with considerably |
| // murkier outlook. The |enterBlockScope()| precipitated by the |
| // outparam-set here initializes the let-binding in |
| // |emitComprehensionFor{In,Of}| with |undefined|, so there's nothing |
| // to do here. |
| MOZ_ASSERT(pn->isLexical()); |
| } else { |
| // This is legacy comprehension syntax. We'll have PNK_LET here, using |
| // a lexical scope provided by/for the entire comprehension. Name |
| // analysis assumes declarations initialize lets, but as we're handling |
| // this declaration manually, we must also initialize manually to avoid |
| // triggering dead zone checks. |
| MOZ_ASSERT(pn->isKind(PNK_LET)); |
| MOZ_ASSERT(pn->pn_count == 1); |
| |
| emittingForInit = true; |
| if (!emitVariables(pn, InitializeVars)) |
| return false; |
| emittingForInit = false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); |
| |
| ParseNode* forHead = pn->pn_left; |
| MOZ_ASSERT(forHead->isKind(PNK_FOROF)); |
| |
| ParseNode* forHeadExpr = forHead->pn_kid3; |
| ParseNode* forBody = pn->pn_right; |
| |
| ptrdiff_t top = offset(); |
| |
| ParseNode* loopDecl = forHead->pn_kid1; |
| bool letBlockScope = false; |
| if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &letBlockScope)) |
| return false; |
| |
| // For-of loops run with two values on the stack: the iterator and the |
| // current result object. |
| |
| // Compile the object expression to the right of 'of'. |
| if (!emitTree(forHeadExpr)) // EXPR |
| return false; |
| if (!emitIterator()) // ITER |
| return false; |
| |
| // Push a dummy result so that we properly enter iteration midstream. |
| if (!emit1(JSOP_UNDEFINED)) // ITER RESULT |
| return false; |
| |
| // Enter the block before the loop body, after evaluating the obj. |
| // Initialize let bindings with undefined when entering, as the name |
| // assigned to is a plain assignment. |
| StmtInfoBCE letStmt(cx); |
| if (letBlockScope) { |
| if (!enterBlockScope(&letStmt, loopDecl->pn_objbox, JSOP_UNDEFINED, 0)) |
| return false; |
| } |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::FOR_OF_LOOP, top); |
| |
| // Jump down to the loop condition to minimize overhead assuming at least |
| // one iteration, as the other loop forms do. Annotate so IonMonkey can |
| // find the loop-closing jump. |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) |
| return false; |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| |
| top = offset(); |
| stmtInfo.setTop(top); |
| if (!emitLoopHead(nullptr)) |
| return false; |
| |
| #ifdef DEBUG |
| int loopDepth = this->stackDepth; |
| #endif |
| |
| // Emit code to assign result.value to the iteration variable. |
| if (!emit1(JSOP_DUP)) // ITER RESULT RESULT |
| return false; |
| if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE |
| return false; |
| if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) // ITER RESULT VALUE |
| return false; |
| if (!emit1(JSOP_POP)) // ITER RESULT |
| return false; |
| |
| // The stack should be balanced around the assignment opcode sequence. |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| // Emit code for the loop body. |
| if (!emitTree(forBody)) |
| return false; |
| |
| // Set loop and enclosing "update" offsets, for continue. |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = offset(); |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| |
| // COME FROM the beginning of the loop to here. |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(forHeadExpr)) |
| return false; |
| |
| if (!emit1(JSOP_POP)) // ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // ITER ITER |
| return false; |
| if (!emitIteratorNext(forHead)) // ITER RESULT |
| return false; |
| if (!emit1(JSOP_DUP)) // ITER RESULT RESULT |
| return false; |
| if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? |
| return false; |
| |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, top - offset(), &beq)) // ITER RESULT |
| return false; |
| |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| // Let Ion know where the closing jump of this loop is. |
| if (!setSrcNoteOffset(noteIndex, 0, beq - jmp)) |
| return false; |
| |
| // Fixup breaks and continues. |
| popStatement(); |
| |
| if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top, offset())) |
| return false; |
| |
| if (letBlockScope) { |
| if (!leaveNestedScope(&letStmt)) |
| return false; |
| } |
| |
| // Pop the result and the iter. |
| return emitUint16Operand(JSOP_POPN, 2); // |
| } |
| |
| bool |
| BytecodeEmitter::emitComprehensionForIn(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); |
| |
| ptrdiff_t top = offset(); |
| |
| ParseNode* forHead = pn->pn_left; |
| MOZ_ASSERT(forHead->isKind(PNK_FORIN)); |
| |
| ParseNode* forBody = pn->pn_right; |
| |
| ParseNode* loopDecl = forHead->pn_kid1; |
| bool letBlockScope = false; |
| if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &letBlockScope)) |
| return false; |
| |
| /* Compile the object expression to the right of 'in'. */ |
| if (!emitTree(forHead->pn_kid3)) |
| return false; |
| |
| /* |
| * Emit a bytecode to convert top of stack value to the iterator |
| * object depending on the loop variant (for-in, for-each-in, or |
| * destructuring for-in). |
| */ |
| MOZ_ASSERT(pn->isOp(JSOP_ITER)); |
| if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags)) |
| return false; |
| |
| // For-in loops have both the iterator and the value on the stack. Push |
| // undefined to balance the stack. |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| |
| // Enter the block before the loop body, after evaluating the obj. |
| // Initialize let bindings with undefined when entering, as the name |
| // assigned to is a plain assignment. |
| StmtInfoBCE letStmt(cx); |
| if (letBlockScope) { |
| if (!enterBlockScope(&letStmt, loopDecl->pn_objbox, JSOP_UNDEFINED, 0)) |
| return false; |
| } |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::FOR_IN_LOOP, top); |
| |
| /* Annotate so IonMonkey can find the loop-closing jump. */ |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) |
| return false; |
| |
| /* |
| * Jump down to the loop condition to minimize overhead assuming at |
| * least one iteration, as the other loop forms do. |
| */ |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| |
| top = offset(); |
| stmtInfo.setTop(top); |
| if (!emitLoopHead(nullptr)) |
| return false; |
| |
| #ifdef DEBUG |
| int loopDepth = this->stackDepth; |
| #endif |
| |
| // Emit code to assign the enumeration value to the left hand side, but |
| // also leave it on the stack. |
| if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) |
| return false; |
| |
| /* The stack should be balanced around the assignment opcode sequence. */ |
| MOZ_ASSERT(this->stackDepth == loopDepth); |
| |
| /* Emit code for the loop body. */ |
| if (!emitTree(forBody)) |
| return false; |
| |
| /* Set loop and enclosing "update" offsets, for continue. */ |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = offset(); |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| |
| /* |
| * Fixup the goto that starts the loop to jump down to JSOP_MOREITER. |
| */ |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(nullptr)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| if (!emit1(JSOP_MOREITER)) |
| return false; |
| if (!emit1(JSOP_ISNOITER)) |
| return false; |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, top - offset(), &beq)) |
| return false; |
| |
| /* Set the srcnote offset so we can find the closing jump. */ |
| if (!setSrcNoteOffset(noteIndex, 0, beq - jmp)) |
| return false; |
| |
| // Fix up breaks and continues. |
| popStatement(); |
| |
| // Pop the enumeration value. |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top, offset())) |
| return false; |
| if (!emit1(JSOP_ENDITER)) |
| return false; |
| |
| if (letBlockScope) { |
| if (!leaveNestedScope(&letStmt)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitComprehensionFor(ParseNode* compFor) |
| { |
| MOZ_ASSERT(compFor->pn_left->isKind(PNK_FORIN) || |
| compFor->pn_left->isKind(PNK_FOROF)); |
| |
| if (!updateLineNumberNotes(compFor->pn_pos.begin)) |
| return false; |
| |
| return compFor->pn_left->isKind(PNK_FORIN) |
| ? emitComprehensionForIn(compFor) |
| : emitComprehensionForOf(compFor); |
| } |
| |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) |
| { |
| FunctionBox* funbox = pn->pn_funbox; |
| RootedFunction fun(cx, funbox->function()); |
| MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); |
| |
| /* |
| * Set the |wasEmitted| flag in the funbox once the function has been |
| * emitted. Function definitions that need hoisting to the top of the |
| * function will be seen by emitFunction in two places. |
| */ |
| if (funbox->wasEmitted) { |
| MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); |
| MOZ_ASSERT(pn->functionIsHoisted()); |
| MOZ_ASSERT(sc->isFunctionBox()); |
| return true; |
| } |
| |
| funbox->wasEmitted = true; |
| |
| /* |
| * Mark as singletons any function which will only be executed once, or |
| * which is inner to a lambda we only expect to run once. In the latter |
| * case, if the lambda runs multiple times then CloneFunctionObject will |
| * make a deep clone of its contents. |
| */ |
| if (fun->isInterpreted()) { |
| bool singleton = checkRunOnceContext(); |
| if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) |
| return false; |
| |
| SharedContext* outersc = sc; |
| if (fun->isInterpretedLazy()) { |
| if (!fun->lazyScript()->sourceObject()) { |
| JSObject* scope = innermostStaticScope(); |
| JSObject* source = script->sourceObject(); |
| fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>()); |
| } |
| if (emittingRunOnceLambda) |
| fun->lazyScript()->setTreatAsRunOnce(); |
| } else { |
| |
| if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals()) |
| funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent |
| MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); |
| |
| // Inherit most things (principals, version, etc) from the |
| // parent. Use default values for the rest. |
| Rooted<JSScript*> parent(cx, script); |
| MOZ_ASSERT(parent->getVersion() == parser->options().version); |
| MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); |
| const TransitiveCompileOptions& transitiveOptions = parser->options(); |
| CompileOptions options(cx, transitiveOptions); |
| |
| Rooted<JSObject*> enclosingScope(cx, innermostStaticScope()); |
| Rooted<JSObject*> sourceObject(cx, script->sourceObject()); |
| Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options, |
| sourceObject, |
| funbox->bufStart, funbox->bufEnd)); |
| if (!script) |
| return false; |
| |
| script->bindings = funbox->bindings; |
| |
| uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); |
| BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, |
| insideEval, evalCaller, |
| insideNonGlobalEval, lineNum, emitterMode); |
| if (!bce2.init()) |
| return false; |
| |
| /* We measured the max scope depth when we parsed the function. */ |
| if (!bce2.emitFunctionScript(pn->pn_body)) |
| return false; |
| |
| if (funbox->isLikelyConstructorWrapper()) |
| script->setLikelyConstructorWrapper(); |
| } |
| if (outersc->isFunctionBox()) |
| outersc->asFunctionBox()->function()->nonLazyScript()->setHasInnerFunctions(true); |
| } else { |
| MOZ_ASSERT(IsAsmJSModuleNative(fun->native())); |
| } |
| |
| /* Make the function object a literal in the outer script's pool. */ |
| unsigned index = objectList.add(pn->pn_funbox); |
| |
| /* Non-hoisted functions simply emit their respective op. */ |
| if (!pn->functionIsHoisted()) { |
| /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ |
| MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); |
| if (fun->isArrow()) { |
| if (sc->allowNewTarget()) { |
| if (!emit1(JSOP_NEWTARGET)) |
| return false; |
| } else { |
| if (!emit1(JSOP_NULL)) |
| return false; |
| } |
| } |
| |
| if (needsProto) { |
| MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); |
| pn->setOp(JSOP_FUNWITHPROTO); |
| } |
| return emitIndex32(pn->getOp(), index); |
| } |
| |
| MOZ_ASSERT(!needsProto); |
| |
| /* |
| * For scripts we put the bytecode for top-level functions in the prologue |
| * to predefine their names in the variable object before the main code is |
| * executed. |
| * |
| * Functions are fully parsed prior to invocation of the emitter and calls |
| * to emitTree for function definitions are scheduled before generating |
| * the rest of code. |
| * |
| * For modules, we record the function and instantiate the binding during |
| * ModuleDeclarationInstantiation(), before the script is run. |
| */ |
| if (sc->isGlobalContext()) { |
| MOZ_ASSERT(pn->pn_scopecoord.isFree()); |
| MOZ_ASSERT(pn->getOp() == JSOP_NOP); |
| MOZ_ASSERT(atBodyLevel()); |
| switchToPrologue(); |
| if (!emitIndex32(JSOP_DEFFUN, index)) |
| return false; |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| switchToMain(); |
| } else if (sc->isFunctionBox()) { |
| #ifdef DEBUG |
| BindingIter bi(script); |
| while (bi->name() != fun->atom()) |
| bi++; |
| MOZ_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT || |
| bi->kind() == Binding::ARGUMENT); |
| MOZ_ASSERT(bi.argOrLocalIndex() < JS_BIT(20)); |
| #endif |
| if (!emitIndexOp(JSOP_LAMBDA, index)) |
| return false; |
| MOZ_ASSERT(pn->getOp() == JSOP_GETLOCAL || pn->getOp() == JSOP_GETARG); |
| JSOp setOp = pn->getOp() == JSOP_GETLOCAL ? JSOP_SETLOCAL : JSOP_SETARG; |
| if (!emitVarOp(pn, setOp)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } else { |
| RootedModuleObject module(cx, sc->asModuleBox()->module()); |
| RootedAtom name(cx, fun->atom()); |
| if (!module->noteFunctionDeclaration(cx, name, fun)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitDo(ParseNode* pn) |
| { |
| /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */ |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_WHILE, ¬eIndex)) |
| return false; |
| if (!emit1(JSOP_NOP)) |
| return false; |
| |
| unsigned noteIndex2; |
| if (!newSrcNote(SRC_WHILE, ¬eIndex2)) |
| return false; |
| |
| /* Compile the loop body. */ |
| ptrdiff_t top = offset(); |
| if (!emitLoopHead(pn->pn_left)) |
| return false; |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::DO_LOOP, top); |
| |
| if (!emitLoopEntry(nullptr)) |
| return false; |
| |
| if (!emitTree(pn->pn_left)) |
| return false; |
| |
| /* Set loop and enclosing label update offsets, for continue. */ |
| ptrdiff_t off = offset(); |
| StmtInfoBCE* stmt = &stmtInfo; |
| do { |
| stmt->update = off; |
| } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL); |
| |
| /* Compile the loop condition, now that continues know where to go. */ |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFNE, top - offset(), &beq)) |
| return false; |
| |
| if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top, offset())) |
| return false; |
| |
| /* |
| * Update the annotations with the update and back edge positions, for |
| * IonBuilder. |
| * |
| * Be careful: We must set noteIndex2 before noteIndex in case the noteIndex |
| * note gets bigger. |
| */ |
| if (!setSrcNoteOffset(noteIndex2, 0, beq - top)) |
| return false; |
| if (!setSrcNoteOffset(noteIndex, 0, 1 + (off - top))) |
| return false; |
| |
| popStatement(); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitWhile(ParseNode* pn) |
| { |
| /* |
| * Minimize bytecodes issued for one or more iterations by jumping to |
| * the condition below the body and closing the loop if the condition |
| * is true with a backward branch. For iteration count i: |
| * |
| * i test at the top test at the bottom |
| * = =============== ================== |
| * 0 ifeq-pass goto; ifne-fail |
| * 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail |
| * 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail |
| * . . . |
| * N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail |
| */ |
| |
| // If we have a single-line while, like "while (x) ;", we want to |
| // emit the line note before the initial goto, so that the |
| // debugger sees a single entry point. This way, if there is a |
| // breakpoint on the line, it will only fire once; and "next"ing |
| // will skip the whole loop. However, for the multi-line case we |
| // want to emit the line note after the initial goto, so that |
| // "cont" stops on each iteration -- but without a stop before the |
| // first iteration. |
| if (parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) == |
| parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end) && |
| !updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| |
| LoopStmtInfo stmtInfo(cx); |
| pushLoopStatement(&stmtInfo, StmtType::WHILE_LOOP, offset()); |
| |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_WHILE, ¬eIndex)) |
| return false; |
| |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| |
| ptrdiff_t top = offset(); |
| if (!emitLoopHead(pn->pn_right)) |
| return false; |
| |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| setJumpOffsetAt(jmp); |
| if (!emitLoopEntry(pn->pn_left)) |
| return false; |
| if (!emitTree(pn->pn_left)) |
| return false; |
| |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFNE, top - offset(), &beq)) |
| return false; |
| |
| if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top, offset())) |
| return false; |
| |
| if (!setSrcNoteOffset(noteIndex, 0, beq - jmp)) |
| return false; |
| |
| popStatement(); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitBreak(PropertyName* label) |
| { |
| StmtInfoBCE* stmt = innermostStmt(); |
| SrcNoteType noteType; |
| if (label) { |
| while (stmt->type != StmtType::LABEL || stmt->label != label) |
| stmt = stmt->enclosing; |
| noteType = SRC_BREAK2LABEL; |
| } else { |
| while (!stmt->isLoop() && stmt->type != StmtType::SWITCH) |
| stmt = stmt->enclosing; |
| noteType = (stmt->type == StmtType::SWITCH) ? SRC_SWITCHBREAK : SRC_BREAK; |
| } |
| |
| return emitGoto(stmt, &stmt->breaks, noteType); |
| } |
| |
| bool |
| BytecodeEmitter::emitContinue(PropertyName* label) |
| { |
| StmtInfoBCE* stmt = innermostStmt(); |
| if (label) { |
| /* Find the loop statement enclosed by the matching label. */ |
| StmtInfoBCE* loop = nullptr; |
| while (stmt->type != StmtType::LABEL || stmt->label != label) { |
| if (stmt->isLoop()) |
| loop = stmt; |
| stmt = stmt->enclosing; |
| } |
| stmt = loop; |
| } else { |
| while (!stmt->isLoop()) |
| stmt = stmt->enclosing; |
| } |
| |
| return emitGoto(stmt, &stmt->continues, SRC_CONTINUE); |
| } |
| |
| |
| bool |
| BytecodeEmitter::emitGetFunctionThis(ParseNode* pn) |
| { |
| MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function); |
| MOZ_ASSERT(pn->isKind(PNK_NAME)); |
| MOZ_ASSERT(pn->name() == cx->names().dotThis); |
| |
| if (!emitTree(pn)) |
| return false; |
| if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_SUPERBASE)); |
| return emitGetFunctionThis(pn->pn_kid); |
| } |
| |
| bool |
| BytecodeEmitter::emitThisLiteral(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_THIS)); |
| |
| if (ParseNode* thisName = pn->pn_kid) |
| return emitGetFunctionThis(thisName); |
| |
| if (sc->thisBinding() == ThisBinding::Module) |
| return emit1(JSOP_UNDEFINED); |
| |
| MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); |
| return emit1(JSOP_GLOBALTHIS); |
| } |
| |
| bool |
| BytecodeEmitter::emitLoadFromTopScope(BindingIter& bi) |
| { |
| if (script->bindingIsAliased(bi)) { |
| ScopeCoordinate sc; |
| sc.setHops(0); |
| sc.setSlot(0); |
| MOZ_ALWAYS_TRUE(lookupAliasedNameSlot(bi->name(), &sc)); |
| return emitAliasedVarOp(JSOP_GETALIASEDVAR, sc, DontCheckLexical); |
| } |
| |
| return emitUnaliasedVarOp(JSOP_GETLOCAL, bi.localIndex(), DontCheckLexical); |
| } |
| |
| bool |
| BytecodeEmitter::emitStoreToTopScope(BindingIter& bi) |
| { |
| if (script->bindingIsAliased(bi)) { |
| ScopeCoordinate sc; |
| sc.setHops(0); |
| sc.setSlot(0); // initialize to silence GCC warning |
| MOZ_ALWAYS_TRUE(lookupAliasedNameSlot(bi->name(), &sc)); |
| return emitAliasedVarOp(JSOP_SETALIASEDVAR, sc, DontCheckLexical); |
| } |
| |
| return emitUnaliasedVarOp(JSOP_SETLOCAL, bi.localIndex(), DontCheckLexical); |
| } |
| |
| bool |
| BytecodeEmitter::emitReturn(ParseNode* pn) |
| { |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| |
| if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { |
| if (!emitPrepareIteratorResult()) |
| return false; |
| } |
| |
| /* Push a return value */ |
| if (ParseNode* pn2 = pn->pn_kid) { |
| if (!emitTree(pn2)) |
| return false; |
| } else { |
| /* No explicit return value provided */ |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| } |
| |
| if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { |
| if (!emitFinishIteratorResult(true)) |
| return false; |
| } |
| |
| /* |
| * EmitNonLocalJumpFixup may add fixup bytecode to close open try |
| * blocks having finally clauses and to exit intermingled let blocks. |
| * We can't simply transfer control flow to our caller in that case, |
| * because we must gosub to those finally clauses from inner to outer, |
| * with the correct stack pointer (i.e., after popping any with, |
| * for/in, etc., slots nested inside the finally's try). |
| * |
| * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an |
| * extra JSOP_RETRVAL after the fixups. |
| */ |
| ptrdiff_t top = offset(); |
| |
| bool isGenerator = sc->isFunctionBox() && sc->asFunctionBox()->isGenerator(); |
| bool isDerivedClassConstructor = |
| sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); |
| |
| if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) |
| return false; |
| |
| NonLocalExitScope nle(this); |
| |
| if (!nle.prepareForNonLocalJump(nullptr)) |
| return false; |
| |
| if (isGenerator) { |
| ScopeCoordinate sc; |
| // We know that .generator is on the top scope chain node, as we just |
| // exited nested scopes. |
| sc.setHops(0); |
| MOZ_ALWAYS_TRUE(lookupAliasedNameSlot(cx->names().dotGenerator, &sc)); |
| if (!emitAliasedVarOp(JSOP_GETALIASEDVAR, sc, DontCheckLexical)) |
| return false; |
| if (!emitYieldOp(JSOP_FINALYIELDRVAL)) |
| return false; |
| } else if (isDerivedClassConstructor) { |
| MOZ_ASSERT(code()[top] == JSOP_SETRVAL); |
| BindingIter bi = Bindings::thisBinding(cx, script); |
| if (!emitLoadFromTopScope(bi)) |
| return false; |
| if (!emit1(JSOP_CHECKRETURN)) |
| return false; |
| if (!emit1(JSOP_RETRVAL)) |
| return false; |
| } else if (top + static_cast<ptrdiff_t>(JSOP_RETURN_LENGTH) != offset()) { |
| code()[top] = JSOP_SETRVAL; |
| if (!emit1(JSOP_RETRVAL)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitYield(ParseNode* pn) |
| { |
| MOZ_ASSERT(sc->isFunctionBox()); |
| |
| if (pn->getOp() == JSOP_YIELD) { |
| if (sc->asFunctionBox()->isStarGenerator()) { |
| if (!emitPrepareIteratorResult()) |
| return false; |
| } |
| if (pn->pn_left) { |
| if (!emitTree(pn->pn_left)) |
| return false; |
| } else { |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| } |
| if (sc->asFunctionBox()->isStarGenerator()) { |
| if (!emitFinishIteratorResult(false)) |
| return false; |
| } |
| } else { |
| MOZ_ASSERT(pn->getOp() == JSOP_INITIALYIELD); |
| } |
| |
| if (!emitTree(pn->pn_right)) |
| return false; |
| |
| if (!emitYieldOp(pn->getOp())) |
| return false; |
| |
| if (pn->getOp() == JSOP_INITIALYIELD && !emit1(JSOP_POP)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) |
| { |
| MOZ_ASSERT(sc->isFunctionBox()); |
| MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); |
| |
| if (!emitTree(iter)) // ITERABLE |
| return false; |
| if (!emitIterator()) // ITER |
| return false; |
| |
| // Initial send value is undefined. |
| if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED |
| return false; |
| |
| int depth = stackDepth; |
| MOZ_ASSERT(depth >= 2); |
| |
| ptrdiff_t initialSend = -1; |
| if (!emitBackPatchOp(&initialSend)) // goto initialSend |
| return false; |
| |
| // Try prologue. // ITER RESULT |
| StmtInfoBCE stmtInfo(cx); |
| pushStatement(&stmtInfo, StmtType::TRY, offset()); |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_TRY, ¬eIndex)) |
| return false; |
| ptrdiff_t tryStart = offset(); // tryStart: |
| if (!emit1(JSOP_TRY)) |
| return false; |
| MOZ_ASSERT(this->stackDepth == depth); |
| |
| // Load the generator object. |
| if (!emitTree(gen)) // ITER RESULT GENOBJ |
| return false; |
| |
| // Yield RESULT as-is, without re-boxing. |
| if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED |
| return false; |
| |
| // Try epilogue. |
| if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart)) |
| return false; |
| ptrdiff_t subsequentSend = -1; |
| if (!emitBackPatchOp(&subsequentSend)) // goto subsequentSend |
| return false; |
| ptrdiff_t tryEnd = offset(); // tryEnd: |
| |
| // Catch location. |
| stackDepth = uint32_t(depth); // ITER RESULT |
| if (!emit1(JSOP_POP)) // ITER |
| return false; |
| // THROW? = 'throw' in ITER |
| if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION |
| return false; |
| if (!emit1(JSOP_SWAP)) // EXCEPTION ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER |
| return false; |
| if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" |
| return false; |
| if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER |
| return false; |
| if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? |
| return false; |
| // if (THROW?) goto delegate |
| ptrdiff_t checkThrow; |
| if (!emitJump(JSOP_IFNE, 0, &checkThrow)) // EXCEPTION ITER |
| return false; |
| if (!emit1(JSOP_POP)) // EXCEPTION |
| return false; |
| if (!emit1(JSOP_THROW)) // throw EXCEPTION |
| return false; |
| |
| setJumpOffsetAt(checkThrow); // delegate: |
| // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER |
| stackDepth = uint32_t(depth); |
| if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER |
| return false; |
| if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW |
| return false; |
| if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER |
| return false; |
| if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION |
| return false; |
| if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT |
| return false; |
| checkTypeSet(JSOP_CALL); |
| MOZ_ASSERT(this->stackDepth == depth); |
| ptrdiff_t checkResult = -1; |
| if (!emitBackPatchOp(&checkResult)) // goto checkResult |
| return false; |
| |
| // Catch epilogue. |
| popStatement(); |
| |
| // This is a peace offering to ReconstructPCStack. See the note in EmitTry. |
| if (!emit1(JSOP_NOP)) |
| return false; |
| if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart + JSOP_TRY_LENGTH, tryEnd)) |
| return false; |
| |
| // After the try/catch block: send the received value to the iterator. |
| backPatch(initialSend, code().end(), JSOP_GOTO); // initialSend: |
| backPatch(subsequentSend, code().end(), JSOP_GOTO); // subsequentSend: |
| |
| // Send location. |
| // result = iter.next(received) // ITER RECEIVED |
| if (!emit1(JSOP_SWAP)) // RECEIVED ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER |
| return false; |
| if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER ITER |
| return false; |
| if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // RECEIVED ITER ITER NEXT |
| return false; |
| if (!emit1(JSOP_SWAP)) // RECEIVED ITER NEXT ITER |
| return false; |
| if (!emit2(JSOP_PICK, 3)) // ITER NEXT ITER RECEIVED |
| return false; |
| if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT |
| return false; |
| checkTypeSet(JSOP_CALL); |
| MOZ_ASSERT(this->stackDepth == depth); |
| |
| backPatch(checkResult, code().end(), JSOP_GOTO); // checkResult: |
| |
| // if (!result.done) goto tryStart; // ITER RESULT |
| if (!emit1(JSOP_DUP)) // ITER RESULT RESULT |
| return false; |
| if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE |
| return false; |
| // if (!DONE) goto tryStart; |
| if (!emitJump(JSOP_IFEQ, tryStart - offset())) // ITER RESULT |
| return false; |
| |
| // result.value |
| if (!emit1(JSOP_SWAP)) // RESULT ITER |
| return false; |
| if (!emit1(JSOP_POP)) // RESULT |
| return false; |
| if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE |
| return false; |
| |
| MOZ_ASSERT(this->stackDepth == depth - 1); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitStatementList(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { |
| if (!emitTree(pn2)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitStatement(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_SEMI)); |
| |
| ParseNode* pn2 = pn->pn_kid; |
| if (!pn2) |
| return true; |
| |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| |
| /* |
| * Top-level or called-from-a-native JS_Execute/EvaluateScript, |
| * debugger, and eval frames may need the value of the ultimate |
| * expression statement as the script's result, despite the fact |
| * that it appears useless to the compiler. |
| * |
| * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when |
| * calling JS_Compile* to suppress JSOP_SETRVAL. |
| */ |
| bool wantval = false; |
| bool useful = false; |
| if (sc->isFunctionBox()) |
| MOZ_ASSERT(!script->noScriptRval()); |
| else |
| useful = wantval = !script->noScriptRval(); |
| |
| /* Don't eliminate expressions with side effects. */ |
| if (!useful) { |
| if (!checkSideEffects(pn2, &useful)) |
| return false; |
| |
| /* |
| * Don't eliminate apparently useless expressions if they are |
| * labeled expression statements. The innermostStmt()->update test |
| * catches the case where we are nesting in emitTree for a labeled |
| * compound statement. |
| */ |
| if (innermostStmt() && |
| innermostStmt()->type == StmtType::LABEL && |
| innermostStmt()->update >= offset()) |
| { |
| useful = true; |
| } |
| } |
| |
| if (useful) { |
| JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; |
| MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP)); |
| if (!emitTree(pn2)) |
| return false; |
| if (!emit1(op)) |
| return false; |
| } else if (pn->isDirectivePrologueMember()) { |
| // Don't complain about directive prologue members; just don't emit |
| // their code. |
| } else { |
| if (JSAtom* atom = pn->isStringExprStatement()) { |
| // Warn if encountering a non-directive prologue member string |
| // expression statement, that is inconsistent with the current |
| // directive prologue. That is, a script *not* starting with |
| // "use strict" should warn for any "use strict" statements seen |
| // later in the script, because such statements are misleading. |
| const char* directive = nullptr; |
| if (atom == cx->names().useStrict) { |
| if (!sc->strictScript) |
| directive = js_useStrict_str; |
| } else if (atom == cx->names().useAsm) { |
| if (sc->isFunctionBox()) { |
| JSFunction* fun = sc->asFunctionBox()->function(); |
| if (fun->isNative() && IsAsmJSModuleNative(fun->native())) |
| directive = js_useAsm_str; |
| } |
| } |
| |
| if (directive) { |
| if (!reportStrictWarning(pn2, JSMSG_CONTRARY_NONDIRECTIVE, directive)) |
| return false; |
| } |
| } else { |
| current->currentLine = parser->tokenStream.srcCoords.lineNum(pn2->pn_pos.begin); |
| current->lastColumn = 0; |
| if (!reportStrictWarning(pn2, JSMSG_USELESS_EXPR)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitDeleteName(ParseNode* node) |
| { |
| MOZ_ASSERT(node->isKind(PNK_DELETENAME)); |
| MOZ_ASSERT(node->isArity(PN_UNARY)); |
| |
| ParseNode* nameExpr = node->pn_kid; |
| MOZ_ASSERT(nameExpr->isKind(PNK_NAME)); |
| |
| if (!bindNameToSlot(nameExpr)) |
| return false; |
| |
| MOZ_ASSERT(nameExpr->isOp(JSOP_DELNAME)); |
| return emitAtomOp(nameExpr, JSOP_DELNAME); |
| } |
| |
| bool |
| BytecodeEmitter::emitDeleteProperty(ParseNode* node) |
| { |
| MOZ_ASSERT(node->isKind(PNK_DELETEPROP)); |
| MOZ_ASSERT(node->isArity(PN_UNARY)); |
| |
| ParseNode* propExpr = node->pn_kid; |
| MOZ_ASSERT(propExpr->isKind(PNK_DOT)); |
| |
| if (propExpr->as<PropertyAccess>().isSuper()) { |
| // Still have to calculate the base, even though we are are going |
| // to throw unconditionally, as calculating the base could also |
| // throw. |
| if (!emit1(JSOP_SUPERBASE)) |
| return false; |
| |
| return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER); |
| } |
| |
| JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; |
| return emitPropOp(propExpr, delOp); |
| } |
| |
| bool |
| BytecodeEmitter::emitDeleteElement(ParseNode* node) |
| { |
| MOZ_ASSERT(node->isKind(PNK_DELETEELEM)); |
| MOZ_ASSERT(node->isArity(PN_UNARY)); |
| |
| ParseNode* elemExpr = node->pn_kid; |
| MOZ_ASSERT(elemExpr->isKind(PNK_ELEM)); |
| |
| if (elemExpr->as<PropertyByValue>().isSuper()) { |
| // Still have to calculate everything, even though we're gonna throw |
| // since it may have side effects |
| if (!emitTree(elemExpr->pn_right)) |
| return false; |
| |
| if (!emit1(JSOP_SUPERBASE)) |
| return false; |
| if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) |
| return false; |
| |
| // Another wrinkle: Balance the stack from the emitter's point of view. |
| // Execution will not reach here, as the last bytecode threw. |
| return emit1(JSOP_POP); |
| } |
| |
| JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; |
| return emitElemOp(elemExpr, delOp); |
| } |
| |
| bool |
| BytecodeEmitter::emitDeleteExpression(ParseNode* node) |
| { |
| MOZ_ASSERT(node->isKind(PNK_DELETEEXPR)); |
| MOZ_ASSERT(node->isArity(PN_UNARY)); |
| |
| ParseNode* expression = node->pn_kid; |
| |
| // If useless, just emit JSOP_TRUE; otherwise convert |delete <expr>| to |
| // effectively |<expr>, true|. |
| bool useful = false; |
| if (!checkSideEffects(expression, &useful)) |
| return false; |
| |
| if (useful) { |
| MOZ_ASSERT_IF(expression->isKind(PNK_CALL), !(expression->pn_xflags & PNX_SETCALL)); |
| if (!emitTree(expression)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| |
| return emit1(JSOP_TRUE); |
| } |
| |
| bool |
| BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) |
| { |
| // Special-casing of callFunction to emit bytecode that directly |
| // invokes the callee with the correct |this| object and arguments. |
| // callFunction(fun, thisArg, arg0, arg1) thus becomes: |
| // - emit lookup for fun |
| // - emit lookup for thisArg |
| // - emit lookups for arg0, arg1 |
| // |
| // argc is set to the amount of actually emitted args and the |
| // emitting of args below is disabled by setting emitArgs to false. |
| if (pn->pn_count < 3) { |
| reportError(pn, JSMSG_MORE_ARGS_NEEDED, "callFunction", "1", "s"); |
| return false; |
| } |
| |
| ParseNode* pn2 = pn->pn_head; |
| ParseNode* funNode = pn2->pn_next; |
| if (!emitTree(funNode)) |
| return false; |
| |
| ParseNode* thisArg = funNode->pn_next; |
| if (!emitTree(thisArg)) |
| return false; |
| |
| bool oldEmittingForInit = emittingForInit; |
| emittingForInit = false; |
| |
| for (ParseNode* argpn = thisArg->pn_next; argpn; argpn = argpn->pn_next) { |
| if (!emitTree(argpn)) |
| return false; |
| } |
| |
| emittingForInit = oldEmittingForInit; |
| |
| uint32_t argc = pn->pn_count - 3; |
| if (!emitCall(pn->getOp(), argc)) |
| return false; |
| |
| checkTypeSet(pn->getOp()); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn) |
| { |
| // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'close') |
| if (pn->pn_count != 4) { |
| reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s"); |
| return false; |
| } |
| |
| ParseNode* funNode = pn->pn_head; // The resumeGenerator node. |
| |
| ParseNode* genNode = funNode->pn_next; |
| if (!emitTree(genNode)) |
| return false; |
| |
| ParseNode* valNode = genNode->pn_next; |
| if (!emitTree(valNode)) |
| return false; |
| |
| ParseNode* kindNode = valNode->pn_next; |
| MOZ_ASSERT(kindNode->isKind(PNK_STRING)); |
| uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->pn_atom); |
| MOZ_ASSERT(!kindNode->pn_next); |
| |
| if (!emitCall(JSOP_RESUME, operand)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) |
| { |
| if (!emit1(JSOP_FORCEINTERPRETER)) |
| return false; |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitCallOrNew(ParseNode* pn) |
| { |
| bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE); |
| /* |
| * Emit callable invocation or operator new (constructor call) code. |
| * First, emit code for the left operand to evaluate the callable or |
| * constructable object expression. |
| * |
| * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc. |
| * This is necessary to interpose the lambda-initialized method read |
| * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by |
| * JSOP_{SET,INIT}PROP. |
| * |
| * Then (or in a call case that has no explicit reference-base |
| * object) we emit JSOP_UNDEFINED to produce the undefined |this| |
| * value required for calls (which non-strict mode functions |
| * will box into the global object). |
| */ |
| uint32_t argc = pn->pn_count - 1; |
| |
| if (argc >= ARGC_LIMIT) { |
| parser->tokenStream.reportError(callop |
| ? JSMSG_TOO_MANY_FUN_ARGS |
| : JSMSG_TOO_MANY_CON_ARGS); |
| return false; |
| } |
| |
| ParseNode* pn2 = pn->pn_head; |
| bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; |
| switch (pn2->getKind()) { |
| case PNK_NAME: |
| if (emitterMode == BytecodeEmitter::SelfHosting && !spread) { |
| // We shouldn't see foo(bar) = x in self-hosted code. |
| MOZ_ASSERT(!(pn->pn_xflags & PNX_SETCALL)); |
| |
| // Calls to "forceInterpreter", "callFunction" or "resumeGenerator" |
| // in self-hosted code generate inline bytecode. |
| if (pn2->name() == cx->names().callFunction) |
| return emitSelfHostedCallFunction(pn); |
| if (pn2->name() == cx->names().resumeGenerator) |
| return emitSelfHostedResumeGenerator(pn); |
| if (pn2->name() == cx->names().forceInterpreter) |
| return emitSelfHostedForceInterpreter(pn); |
| // Fall through. |
| } |
| if (!emitNameOp(pn2, callop)) |
| return false; |
| break; |
| case PNK_DOT: |
| if (pn2->as<PropertyAccess>().isSuper()) { |
| if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop)) |
| return false; |
| } else { |
| if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP)) |
| return false; |
| } |
| break; |
| case PNK_ELEM: |
| if (pn2->as<PropertyByValue>().isSuper()) { |
| if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop)) |
| return false; |
| } else { |
| if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM)) |
| return false; |
| if (callop) { |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| } |
| } |
| break; |
| case PNK_FUNCTION: |
| /* |
| * Top level lambdas which are immediately invoked should be |
| * treated as only running once. Every time they execute we will |
| * create new types and scripts for their contents, to increase |
| * the quality of type information within them and enable more |
| * backend optimizations. Note that this does not depend on the |
| * lambda being invoked at most once (it may be named or be |
| * accessed via foo.caller indirection), as multiple executions |
| * will just cause the inner scripts to be repeatedly cloned. |
| */ |
| MOZ_ASSERT(!emittingRunOnceLambda); |
| if (checkRunOnceContext()) { |
| emittingRunOnceLambda = true; |
| if (!emitTree(pn2)) |
| return false; |
| emittingRunOnceLambda = false; |
| } else { |
| if (!emitTree(pn2)) |
| return false; |
| } |
| callop = false; |
| break; |
| case PNK_SUPERBASE: |
| MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); |
| MOZ_ASSERT(parser->handler.isSuperBase(pn2)); |
| if (!emit1(JSOP_SUPERFUN)) |
| return false; |
| break; |
| default: |
| if (!emitTree(pn2)) |
| return false; |
| callop = false; /* trigger JSOP_UNDEFINED after */ |
| break; |
| } |
| if (!callop) { |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| } |
| |
| bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || |
| pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;; |
| |
| /* |
| * Emit code for each argument in order, then emit the JSOP_*CALL or |
| * JSOP_NEW bytecode with a two-byte immediate telling how many args |
| * were pushed on the operand stack. |
| */ |
| bool oldEmittingForInit = emittingForInit; |
| emittingForInit = false; |
| if (!spread) { |
| for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { |
| if (!emitTree(pn3)) |
| return false; |
| } |
| |
| if (isNewOp) { |
| if (pn->isKind(PNK_SUPERCALL)) { |
| if (!emit1(JSOP_NEWTARGET)) |
| return false; |
| } else { |
| // Repush the callee as new.target |
| if (!emitDupAt(argc + 1)) |
| return false; |
| } |
| } |
| } else { |
| if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY)) |
| return false; |
| |
| if (isNewOp) { |
| if (pn->isKind(PNK_SUPERCALL)) { |
| if (!emit1(JSOP_NEWTARGET)) |
| return false; |
| } else { |
| if (!emitDupAt(2)) |
| return false; |
| } |
| } |
| } |
| emittingForInit = oldEmittingForInit; |
| |
| if (!spread) { |
| if (!emitCall(pn->getOp(), argc, pn)) |
| return false; |
| } else { |
| if (!emit1(pn->getOp())) |
| return false; |
| } |
| checkTypeSet(pn->getOp()); |
| if (pn->isOp(JSOP_EVAL) || |
| pn->isOp(JSOP_STRICTEVAL) || |
| pn->isOp(JSOP_SPREADEVAL) || |
| pn->isOp(JSOP_STRICTSPREADEVAL)) |
| { |
| uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); |
| if (!emitUint32Operand(JSOP_LINENO, lineNum)) |
| return false; |
| } |
| if (pn->pn_xflags & PNX_SETCALL) { |
| if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitRightAssociative(ParseNode* pn) |
| { |
| // ** is the only right-associative operator. |
| MOZ_ASSERT(pn->isKind(PNK_POW)); |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| |
| // Right-associative operator chain. |
| for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) { |
| if (!emitTree(subexpr)) |
| return false; |
| } |
| for (uint32_t i = 0; i < pn->pn_count - 1; i++) { |
| if (!emit1(JSOP_POW)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitLeftAssociative(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| |
| // Left-associative operator chain. |
| if (!emitTree(pn->pn_head)) |
| return false; |
| JSOp op = pn->getOp(); |
| ParseNode* nextExpr = pn->pn_head->pn_next; |
| do { |
| if (!emitTree(nextExpr)) |
| return false; |
| if (!emit1(op)) |
| return false; |
| } while ((nextExpr = nextExpr->pn_next)); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitLogical(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isArity(PN_LIST)); |
| |
| /* |
| * JSOP_OR converts the operand on the stack to boolean, leaves the original |
| * value on the stack and jumps if true; otherwise it falls into the next |
| * bytecode, which pops the left operand and then evaluates the right operand. |
| * The jump goes around the right operand evaluation. |
| * |
| * JSOP_AND converts the operand on the stack to boolean and jumps if false; |
| * otherwise it falls into the right operand's bytecode. |
| */ |
| |
| /* Left-associative operator chain: avoid too much recursion. */ |
| ParseNode* pn2 = pn->pn_head; |
| if (!emitTree(pn2)) |
| return false; |
| ptrdiff_t top; |
| if (!emitJump(JSOP_BACKPATCH, 0, &top)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| /* Emit nodes between the head and the tail. */ |
| ptrdiff_t jmp = top; |
| while ((pn2 = pn2->pn_next)->pn_next) { |
| if (!emitTree(pn2)) |
| return false; |
| ptrdiff_t off; |
| if (!emitJump(JSOP_BACKPATCH, 0, &off)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| SET_JUMP_OFFSET(code(jmp), off - jmp); |
| jmp = off; |
| } |
| if (!emitTree(pn2)) |
| return false; |
| |
| pn2 = pn->pn_head; |
| ptrdiff_t off = offset(); |
| do { |
| jsbytecode* pc = code(top); |
| ptrdiff_t tmp = GET_JUMP_OFFSET(pc); |
| SET_JUMP_OFFSET(pc, off - top); |
| *pc = pn->getOp(); |
| top += tmp; |
| } while ((pn2 = pn2->pn_next)->pn_next); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSequenceExpr(ParseNode* pn) |
| { |
| for (ParseNode* child = pn->pn_head; ; child = child->pn_next) { |
| if (!updateSourceCoordNotes(child->pn_pos.begin)) |
| return false; |
| if (!emitTree(child)) |
| return false; |
| if (!child->pn_next) |
| break; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| return true; |
| } |
| |
| // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
| // the comment on emitSwitch. |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitIncOrDec(ParseNode* pn) |
| { |
| /* Emit lvalue-specialized code for ++/-- operators. */ |
| ParseNode* pn2 = pn->pn_kid; |
| switch (pn2->getKind()) { |
| case PNK_DOT: |
| if (!emitPropIncDec(pn)) |
| return false; |
| break; |
| case PNK_ELEM: |
| if (!emitElemIncDec(pn)) |
| return false; |
| break; |
| case PNK_CALL: |
| MOZ_ASSERT(pn2->pn_xflags & PNX_SETCALL); |
| if (!emitTree(pn2)) |
| return false; |
| break; |
| default: |
| MOZ_ASSERT(pn2->isKind(PNK_NAME)); |
| pn2->setOp(JSOP_SETNAME); |
| if (!bindNameToSlot(pn2)) |
| return false; |
| JSOp op = pn2->getOp(); |
| bool maySet; |
| switch (op) { |
| case JSOP_SETLOCAL: |
| case JSOP_SETARG: |
| case JSOP_SETALIASEDVAR: |
| case JSOP_SETNAME: |
| case JSOP_STRICTSETNAME: |
| case JSOP_SETGNAME: |
| case JSOP_STRICTSETGNAME: |
| maySet = true; |
| break; |
| default: |
| maySet = false; |
| } |
| if (op == JSOP_CALLEE) { |
| if (!emit1(op)) |
| return false; |
| } else if (!pn2->pn_scopecoord.isFree()) { |
| if (maySet) { |
| if (!emitVarIncDec(pn)) |
| return false; |
| } else { |
| if (!emitVarOp(pn2, op)) |
| return false; |
| } |
| } else { |
| MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
| if (maySet) { |
| if (!emitNameIncDec(pn)) |
| return false; |
| } else { |
| if (!emitAtomOp(pn2, op)) |
| return false; |
| } |
| break; |
| } |
| if (pn2->isConst()) { |
| if (!emit1(JSOP_POS)) |
| return false; |
| bool post; |
| JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
| if (!post) { |
| if (!emit1(JSOP_ONE)) |
| return false; |
| if (!emit1(binop)) |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
| // the comment on emitSwitch. |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitLabeledStatement(const LabeledStatement* pn) |
| { |
| /* |
| * Emit a JSOP_LABEL instruction. The argument is the offset to the statement |
| * following the labeled statement. |
| */ |
| jsatomid index; |
| if (!makeAtomIndex(pn->label(), &index)) |
| return false; |
| |
| ptrdiff_t top; |
| if (!emitJump(JSOP_LABEL, 0, &top)) |
| return false; |
| |
| /* Emit code for the labeled statement. */ |
| StmtInfoBCE stmtInfo(cx); |
| pushStatement(&stmtInfo, StmtType::LABEL, offset()); |
| stmtInfo.label = pn->label(); |
| |
| if (!emitTree(pn->statement())) |
| return false; |
| |
| popStatement(); |
| |
| /* Patch the JSOP_LABEL offset. */ |
| setJumpOffsetAt(top); |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) |
| { |
| /* Emit the condition, then branch if false to the else part. */ |
| if (!emitTree(&conditional.condition())) |
| return false; |
| |
| unsigned noteIndex; |
| if (!newSrcNote(SRC_COND, ¬eIndex)) |
| return false; |
| |
| ptrdiff_t beq; |
| if (!emitJump(JSOP_IFEQ, 0, &beq)) |
| return false; |
| |
| if (!emitTree(&conditional.thenExpression())) |
| return false; |
| |
| /* Jump around else, fixup the branch, emit else, fixup jump. */ |
| ptrdiff_t jmp; |
| if (!emitJump(JSOP_GOTO, 0, &jmp)) |
| return false; |
| setJumpOffsetAt(beq); |
| |
| /* |
| * Because each branch pushes a single value, but our stack budgeting |
| * analysis ignores branches, we now have to adjust this->stackDepth to |
| * ignore the value pushed by the first branch. Execution will follow |
| * only one path, so we must decrement this->stackDepth. |
| * |
| * Failing to do this will foil code, such as let block code generation, |
| * which must use the stack depth to compute local stack indexes correctly. |
| */ |
| MOZ_ASSERT(stackDepth > 0); |
| stackDepth--; |
| if (!emitTree(&conditional.elseExpression())) |
| return false; |
| setJumpOffsetAt(jmp); |
| return setSrcNoteOffset(noteIndex, 0, jmp - beq); |
| } |
| |
| bool |
| BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type) |
| { |
| for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { |
| if (!updateSourceCoordNotes(propdef->pn_pos.begin)) |
| return false; |
| |
| // Handle __proto__: v specially because *only* this form, and no other |
| // involving "__proto__", performs [[Prototype]] mutation. |
| if (propdef->isKind(PNK_MUTATEPROTO)) { |
| MOZ_ASSERT(type == ObjectLiteral); |
| if (!emitTree(propdef->pn_kid)) |
| return false; |
| objp.set(nullptr); |
| if (!emit1(JSOP_MUTATEPROTO)) |
| return false; |
| continue; |
| } |
| |
| bool extraPop = false; |
| if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) { |
| extraPop = true; |
| if (!emit1(JSOP_DUP2)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| |
| /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ |
| ParseNode* key = propdef->pn_left; |
| bool isIndex = false; |
| if (key->isKind(PNK_NUMBER)) { |
| if (!emitNumberOp(key->pn_dval)) |
| return false; |
| isIndex = true; |
| } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { |
| // EmitClass took care of constructor already. |
| if (type == ClassBody && key->pn_atom == cx->names().constructor && |
| !propdef->as<ClassMethod>().isStatic()) |
| { |
| continue; |
| } |
| |
| // The parser already checked for atoms representing indexes and |
| // used PNK_NUMBER instead, but also watch for ids which TI treats |
| // as indexes for simpliciation of downstream analysis. |
| jsid id = NameToId(key->pn_atom->asPropertyName()); |
| if (id != IdToTypeId(id)) { |
| if (!emitTree(key)) |
| return false; |
| isIndex = true; |
| } |
| } else { |
| if (!emitComputedPropertyName(key)) |
| return false; |
| isIndex = true; |
| } |
| |
| /* Emit code for the property initializer. */ |
| if (!emitTree(propdef->pn_right)) |
| return false; |
| |
| JSOp op = propdef->getOp(); |
| MOZ_ASSERT(op == JSOP_INITPROP || |
| op == JSOP_INITPROP_GETTER || |
| op == JSOP_INITPROP_SETTER); |
| |
| if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) |
| objp.set(nullptr); |
| |
| if (propdef->pn_right->isKind(PNK_FUNCTION) && |
| propdef->pn_right->pn_funbox->needsHomeObject()) |
| { |
| MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty()); |
| if (!emit2(JSOP_INITHOMEOBJECT, isIndex)) |
| return false; |
| } |
| |
| // Class methods are not enumerable. |
| if (type == ClassBody) { |
| switch (op) { |
| case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; |
| case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; |
| case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; |
| default: MOZ_CRASH("Invalid op"); |
| } |
| } |
| |
| if (isIndex) { |
| objp.set(nullptr); |
| switch (op) { |
| case JSOP_INITPROP: op = JSOP_INITELEM; break; |
| case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; |
| case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; |
| case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; |
| case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; |
| case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; |
| default: MOZ_CRASH("Invalid op"); |
| } |
| if (!emit1(op)) |
| return false; |
| } else { |
| MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); |
| |
| jsatomid index; |
| if (!makeAtomIndex(key->pn_atom, &index)) |
| return false; |
| |
| if (objp) { |
| MOZ_ASSERT(type == ObjectLiteral); |
| MOZ_ASSERT(!IsHiddenInitOp(op)); |
| MOZ_ASSERT(!objp->inDictionaryMode()); |
| Rooted<jsid> id(cx, AtomToId(key->pn_atom)); |
| RootedValue undefinedValue(cx, UndefinedValue()); |
| if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr, |
| JSPROP_ENUMERATE)) |
| { |
| return false; |
| } |
| if (objp->inDictionaryMode()) |
| objp.set(nullptr); |
| } |
| |
| if (!emitIndex32(op, index)) |
| return false; |
| } |
| |
| if (extraPop) { |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
| // the comment on emitSwitch. |
| MOZ_NEVER_INLINE bool |
| BytecodeEmitter::emitObject(ParseNode* pn) |
| { |
| if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext()) |
| return emitSingletonInitialiser(pn); |
| |
| /* |
| * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing |
| * a new object and defining (in source order) each property on the object |
| * (or mutating the object's [[Prototype]], in the case of __proto__). |
| */ |
| ptrdiff_t offset = this->offset(); |
| if (!emitNewInit(JSProto_Object)) |
| return false; |
| |
| /* |
| * Try to construct the shape of the object as we go, so we can emit a |
| * JSOP_NEWOBJECT with the final shape instead. |
| */ |
| RootedPlainObject obj(cx); |
| // No need to do any guessing for the object kind, since we know exactly |
| // how many properties we plan to have. |
| gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count); |
| obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject); |
| if (!obj) |
| return false; |
| |
| if (!emitPropertyList(pn, &obj, ObjectLiteral)) |
| return false; |
| |
| if (obj) { |
| /* |
| * The object survived and has a predictable shape: update the original |
| * bytecode. |
| */ |
| ObjectBox* objbox = parser->newObjectBox(obj); |
| if (!objbox) |
| return false; |
| |
| static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, |
| "newinit and newobject must have equal length to edit in-place"); |
| |
| uint32_t index = objectList.add(objbox); |
| jsbytecode* code = this->code(offset); |
| code[0] = JSOP_NEWOBJECT; |
| code[1] = jsbytecode(index >> 24); |
| code[2] = jsbytecode(index >> 16); |
| code[3] = jsbytecode(index >> 8); |
| code[4] = jsbytecode(index); |
| } |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitArrayComp(ParseNode* pn) |
| { |
| if (!emitNewInit(JSProto_Array)) |
| return false; |
| |
| /* |
| * Pass the new array's stack index to the PNK_ARRAYPUSH case via |
| * arrayCompDepth, then simply traverse the PNK_FOR node and |
| * its kids under pn2 to generate this comprehension. |
| */ |
| MOZ_ASSERT(stackDepth > 0); |
| uint32_t saveDepth = arrayCompDepth; |
| arrayCompDepth = (uint32_t) (stackDepth - 1); |
| if (!emitTree(pn->pn_head)) |
| return false; |
| arrayCompDepth = saveDepth; |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitSpread() |
| { |
| return emitForOf(StmtType::SPREAD, nullptr); |
| } |
| |
| bool |
| BytecodeEmitter::emitArrayLiteral(ParseNode* pn) |
| { |
| if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) { |
| if (checkSingletonContext()) { |
| // Bake in the object entirely if it will only be created once. |
| return emitSingletonInitialiser(pn); |
| } |
| |
| // If the array consists entirely of primitive values, make a |
| // template object with copy on write elements that can be reused |
| // every time the initializer executes. |
| if (emitterMode != BytecodeEmitter::SelfHosting && pn->pn_count != 0) { |
| RootedValue value(cx); |
| if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value)) |
| return false; |
| if (!value.isMagic(JS_GENERIC_MAGIC)) { |
| // Note: the group of the template object might not yet reflect |
| // that the object has copy on write elements. When the |
| // interpreter or JIT compiler fetches the template, it should |
| // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the |
| // group for the template is accurate. We don't do this here as we |
| // want to use ObjectGroup::allocationSiteGroup, which requires a |
| // finished script. |
| JSObject* obj = &value.toObject(); |
| MOZ_ASSERT(obj->is<ArrayObject>() && |
| obj->as<ArrayObject>().denseElementsAreCopyOnWrite()); |
| |
| ObjectBox* objbox = parser->newObjectBox(obj); |
| if (!objbox) |
| return false; |
| |
| return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE); |
| } |
| } |
| } |
| |
| return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY); |
| } |
| |
| bool |
| BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) |
| { |
| |
| /* |
| * Emit code for [a, b, c] that is equivalent to constructing a new |
| * array and in source order evaluating each element value and adding |
| * it to the array, without invoking latent setters. We use the |
| * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and |
| * to avoid dup'ing and popping the array as each element is added, as |
| * JSOP_SETELEM/JSOP_SETPROP would do. |
| */ |
| MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY); |
| |
| uint32_t nspread = 0; |
| for (ParseNode* elt = pn; elt; elt = elt->pn_next) { |
| if (elt->isKind(PNK_SPREAD)) |
| nspread++; |
| } |
| |
| // Array literal's length is limited to NELEMENTS_LIMIT in parser. |
| static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX, |
| "array literals' maximum length must not exceed limits " |
| "required by BaselineCompiler::emit_JSOP_NEWARRAY, " |
| "BaselineCompiler::emit_JSOP_INITELEM_ARRAY, " |
| "and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY"); |
| MOZ_ASSERT(count >= nspread); |
| MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT, |
| "the parser must throw an error if the array exceeds maximum " |
| "length"); |
| |
| // For arrays with spread, this is a very pessimistic allocation, the |
| // minimum possible final size. |
| if (!emitUint32Operand(op, count - nspread)) // ARRAY |
| return false; |
| |
| ParseNode* pn2 = pn; |
| uint32_t index; |
| bool afterSpread = false; |
| for (index = 0; pn2; index++, pn2 = pn2->pn_next) { |
| if (!afterSpread && pn2->isKind(PNK_SPREAD)) { |
| afterSpread = true; |
| if (!emitNumberOp(index)) // ARRAY INDEX |
| return false; |
| } |
| if (!updateSourceCoordNotes(pn2->pn_pos.begin)) |
| return false; |
| if (pn2->isKind(PNK_ELISION)) { |
| if (!emit1(JSOP_HOLE)) |
| return false; |
| } else { |
| ParseNode* expr = pn2->isKind(PNK_SPREAD) ? pn2->pn_kid : pn2; |
| if (!emitTree(expr)) // ARRAY INDEX? VALUE |
| return false; |
| } |
| if (pn2->isKind(PNK_SPREAD)) { |
| if (!emitIterator()) // ARRAY INDEX ITER |
| return false; |
| if (!emit2(JSOP_PICK, 2)) // INDEX ITER ARRAY |
| return false; |
| if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX |
| return false; |
| if (!emitSpread()) // ARRAY INDEX |
| return false; |
| } else if (afterSpread) { |
| if (!emit1(JSOP_INITELEM_INC)) |
| return false; |
| } else { |
| if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index)) |
| return false; |
| } |
| } |
| MOZ_ASSERT(index == count); |
| if (afterSpread) { |
| if (!emit1(JSOP_POP)) // ARRAY |
| return false; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitUnary(ParseNode* pn) |
| { |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| |
| /* Unary op, including unary +/-. */ |
| JSOp op = pn->getOp(); |
| ParseNode* pn2 = pn->pn_kid; |
| |
| bool oldEmittingForInit = emittingForInit; |
| emittingForInit = false; |
| if (!emitTree(pn2)) |
| return false; |
| |
| emittingForInit = oldEmittingForInit; |
| return emit1(op); |
| } |
| |
| bool |
| BytecodeEmitter::emitTypeof(ParseNode* node, JSOp op) |
| { |
| MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR); |
| |
| if (!updateSourceCoordNotes(node->pn_pos.begin)) |
| return false; |
| |
| bool oldEmittingForInit = emittingForInit; |
| emittingForInit = false; |
| if (!emitTree(node->pn_kid)) |
| return false; |
| |
| emittingForInit = oldEmittingForInit; |
| return emit1(op); |
| } |
| |
| bool |
| BytecodeEmitter::emitArgsBody(ParseNode *pn) |
| { |
| RootedFunction fun(cx, sc->asFunctionBox()->function()); |
| ParseNode* pnlast = pn->last(); |
| |
| // Carefully emit everything in the right order: |
| // 1. Defaults and Destructuring for each argument |
| // 2. Functions |
| ParseNode* pnchild = pnlast->pn_head; |
| bool hasDefaults = sc->asFunctionBox()->hasDefaults(); |
| ParseNode* rest = nullptr; |
| bool restIsDefn = false; |
| if (fun->hasRest() && hasDefaults) { |
| // Defaults with a rest parameter need special handling. The |
| // rest parameter needs to be undefined while defaults are being |
| // processed. To do this, we create the rest argument and let it |
| // sit on the stack while processing defaults. The rest |
| // parameter's slot is set to undefined for the course of |
| // default processing. |
| rest = pn->pn_head; |
| while (rest->pn_next != pnlast) |
| rest = rest->pn_next; |
| restIsDefn = rest->isDefn(); |
| if (!emit1(JSOP_REST)) |
| return false; |
| checkTypeSet(JSOP_REST); |
| |
| // Only set the rest parameter if it's not aliased by a nested |
| // function in the body. |
| if (restIsDefn) { |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!bindNameToSlot(rest)) |
| return false; |
| if (!emitVarOp(rest, JSOP_SETARG)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| if (!emitDefaultsAndDestructuring(pn)) |
| return false; |
| if (fun->hasRest() && hasDefaults) { |
| if (restIsDefn && !emitVarOp(rest, JSOP_SETARG)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| for (ParseNode* pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) { |
| // Only bind the parameter if it's not aliased by a nested function |
| // in the body. |
| if (!pn2->isDefn()) |
| continue; |
| if (!bindNameToSlot(pn2)) |
| return false; |
| if (pn2->pn_next == pnlast && fun->hasRest() && !hasDefaults) { |
| // Fill rest parameter. We handled the case with defaults above. |
| switchToPrologue(); |
| if (!emit1(JSOP_REST)) |
| return false; |
| checkTypeSet(JSOP_REST); |
| if (!emitVarOp(pn2, JSOP_SETARG)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| switchToMain(); |
| } |
| } |
| if (pnlast->pn_xflags & PNX_FUNCDEFS) { |
| // This block contains top-level function definitions. To ensure |
| // that we emit the bytecode defining them before the rest of code |
| // in the block we use a separate pass over functions. During the |
| // main pass later the emitter will add JSOP_NOP with source notes |
| // for the function to preserve the original functions position |
| // when decompiling. |
| // |
| // Currently this is used only for functions, as compile-as-we go |
| // mode for scripts does not allow separate emitter passes. |
| for (ParseNode* pn2 = pnchild; pn2; pn2 = pn2->pn_next) { |
| if (pn2->isKind(PNK_FUNCTION) && pn2->functionIsHoisted()) { |
| if (!emitTree(pn2)) |
| return false; |
| } |
| } |
| } |
| return emitTree(pnlast); |
| } |
| |
| bool |
| BytecodeEmitter::emitDefaultsAndDestructuring(ParseNode* pn) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_ARGSBODY)); |
| |
| ParseNode* pnlast = pn->last(); |
| for (ParseNode* arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) { |
| MOZ_ASSERT(arg->isKind(PNK_NAME) || arg->isKind(PNK_ASSIGN)); |
| ParseNode* argName = nullptr; |
| ParseNode* defNode = nullptr; |
| ParseNode* destruct = nullptr; |
| if (arg->isKind(PNK_ASSIGN)) { |
| argName = arg->pn_left; |
| defNode = arg->pn_right; |
| } else if (arg->pn_atom == cx->names().empty) { |
| argName = arg; |
| destruct = arg->expr(); |
| MOZ_ASSERT(destruct); |
| if (destruct->isKind(PNK_ASSIGN)) { |
| defNode = destruct->pn_right; |
| destruct = destruct->pn_left; |
| } |
| } |
| if (defNode) { |
| if (!bindNameToSlot(argName)) |
| return false; |
| if (!emitVarOp(argName, JSOP_GETARG)) |
| return false; |
| if (!emit1(JSOP_UNDEFINED)) |
| return false; |
| if (!emit1(JSOP_STRICTEQ)) |
| return false; |
| // Emit source note to enable ion compilation. |
| if (!newSrcNote(SRC_IF)) |
| return false; |
| ptrdiff_t jump; |
| if (!emitJump(JSOP_IFEQ, 0, &jump)) |
| return false; |
| if (!emitTree(defNode)) |
| return false; |
| if (!emitVarOp(argName, JSOP_SETARG)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| SET_JUMP_OFFSET(code(jump), offset() - jump); |
| } |
| if (destruct) { |
| if (!emitTree(argName)) |
| return false; |
| if (!emitDestructuringOps(destruct, false)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| bool |
| BytecodeEmitter::emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp) |
| { |
| MOZ_ASSERT(pn->isKind(PNK_NAME)); |
| |
| if (!bindNameToSlot(pn)) |
| return false; |
| |
| jsatomid atomIndex; |
| if (!maybeEmitVarDecl(globalDefOp, pn, &atomIndex)) |
| return false; |
| |
| if (!pn->pn_scopecoord.isFree()) { |
| if (!emitVarOp(pn, pn->getOp())) |
| return false; |
| } else { |
| if (!emitIndexOp(pn->getOp(), atomIndex)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 |
| // (BindingClassDeclarationEvaluation). |
| bool |
| BytecodeEmitter::emitClass(ParseNode* pn) |
| { |
| ClassNode& classNode = pn->as<ClassNode>(); |
| |
| ClassNames* names = classNode.names(); |
| |
| ParseNode* heritageExpression = classNode.heritage(); |
| |
| ParseNode* classMethods = classNode.methodList(); |
| ParseNode* constructor = nullptr; |
| for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) { |
| ClassMethod& method = mn->as<ClassMethod>(); |
| ParseNode& methodName = method.name(); |
| if (!method.isStatic() && |
| methodName.isKind(PNK_OBJECT_PROPERTY_NAME) && |
| methodName.pn_atom == cx->names().constructor) |
| { |
| constructor = &method.method(); |
| break; |
| } |
| } |
| |
| bool savedStrictness = sc->setLocalStrictMode(true); |
| |
| StmtInfoBCE stmtInfo(cx); |
| if (names) { |
| if (!enterBlockScope(&stmtInfo, classNode.scopeObject(), JSOP_UNINITIALIZED)) |
| return false; |
| } |
| |
| // This is kind of silly. In order to the get the home object defined on |
| // the constructor, we have to make it second, but we want the prototype |
| // on top for EmitPropertyList, because we expect static properties to be |
| // rarer. The result is a few more swaps than we would like. Such is life. |
| if (heritageExpression) { |
| if (!emitTree(heritageExpression)) |
| return false; |
| if (!emit1(JSOP_CLASSHERITAGE)) |
| return false; |
| if (!emit1(JSOP_OBJWITHPROTO)) |
| return false; |
| |
| // JSOP_CLASSHERITAGE leaves both protos on the stack. After |
| // creating the prototype, swap it to the bottom to make the |
| // constructor. |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| } else { |
| if (!emitNewInit(JSProto_Object)) |
| return false; |
| } |
| |
| if (constructor) { |
| if (!emitFunction(constructor, !!heritageExpression)) |
| return false; |
| if (constructor->pn_funbox->needsHomeObject()) { |
| if (!emit2(JSOP_INITHOMEOBJECT, 0)) |
| return false; |
| } |
| } else { |
| JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; |
| if (heritageExpression) { |
| if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) |
| return false; |
| } else { |
| if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) |
| return false; |
| } |
| } |
| |
| if (!emit1(JSOP_SWAP)) |
| return false; |
| |
| if (!emit1(JSOP_DUP2)) |
| return false; |
| if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) |
| return false; |
| if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) |
| return false; |
| |
| RootedPlainObject obj(cx); |
| if (!emitPropertyList(classMethods, &obj, ClassBody)) |
| return false; |
| |
| if (!emit1(JSOP_POP)) |
| return false; |
| |
| if (names) { |
| // That DEFCONST is never gonna be used, but use it here for logical consistency. |
| ParseNode* innerName = names->innerBinding(); |
| if (!emitLexicalInitialization(innerName, JSOP_DEFCONST)) |
| return false; |
| |
| if (!leaveNestedScope(&stmtInfo)) |
| return false; |
| |
| ParseNode* outerName = names->outerBinding(); |
| if (outerName) { |
| if (!emitLexicalInitialization(outerName, JSOP_DEFLET)) |
| return false; |
| // Only class statements make outer bindings, and they do not leave |
| // themselves on the stack. |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| } |
| |
| MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); |
| |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) |
| { |
| JS_CHECK_RECURSION(cx, return false); |
| |
| EmitLevelManager elm(this); |
| |
| /* Emit notes to tell the current bytecode's source line number. |
| However, a couple trees require special treatment; see the |
| relevant emitter functions for details. */ |
| if (emitLineNote == EMIT_LINENOTE && pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR && |
| !updateLineNumberNotes(pn->pn_pos.begin)) |
| return false; |
| |
| switch (pn->getKind()) { |
| case PNK_FUNCTION: |
| if (!emitFunction(pn)) |
| return false; |
| break; |
| |
| case PNK_ARGSBODY: |
| if (!emitArgsBody(pn)) |
| return false; |
| break; |
| |
| case PNK_IF: |
| if (!emitIf(pn)) |
| return false; |
| break; |
| |
| case PNK_SWITCH: |
| if (!emitSwitch(pn)) |
| return false; |
| break; |
| |
| case PNK_WHILE: |
| if (!emitWhile(pn)) |
| return false; |
| break; |
| |
| case PNK_DOWHILE: |
| if (!emitDo(pn)) |
| return false; |
| break; |
| |
| case PNK_FOR: |
| if (!emitFor(pn)) |
| return false; |
| break; |
| |
| case PNK_COMPREHENSIONFOR: |
| if (!emitComprehensionFor(pn)) |
| return false; |
| break; |
| |
| case PNK_BREAK: |
| if (!emitBreak(pn->as<BreakStatement>().label())) |
| return false; |
| break; |
| |
| case PNK_CONTINUE: |
| if (!emitContinue(pn->as<ContinueStatement>().label())) |
| return false; |
| break; |
| |
| case PNK_WITH: |
| if (!emitWith(pn)) |
| return false; |
| break; |
| |
| case PNK_TRY: |
| if (!emitTry(pn)) |
| return false; |
| break; |
| |
| case PNK_CATCH: |
| if (!emitCatch(pn)) |
| return false; |
| break; |
| |
| case PNK_VAR: |
| if (!emitVariables(pn, InitializeVars)) |
| return false; |
| break; |
| |
| case PNK_RETURN: |
| if (!emitReturn(pn)) |
| return false; |
| break; |
| |
| case PNK_YIELD_STAR: |
| if (!emitYieldStar(pn->pn_left, pn->pn_right)) |
| return false; |
| break; |
| |
| case PNK_GENERATOR: |
| if (!emit1(JSOP_GENERATOR)) |
| return false; |
| break; |
| |
| case PNK_YIELD: |
| if (!emitYield(pn)) |
| return false; |
| break; |
| |
| case PNK_STATEMENTLIST: |
| if (!emitStatementList(pn)) |
| return false; |
| break; |
| |
| case PNK_SEMI: |
| if (!emitStatement(pn)) |
| return false; |
| break; |
| |
| case PNK_LABEL: |
| if (!emitLabeledStatement(&pn->as<LabeledStatement>())) |
| return false; |
| break; |
| |
| case PNK_COMMA: |
| if (!emitSequenceExpr(pn)) |
| return false; |
| break; |
| |
| case PNK_ASSIGN: |
| case PNK_ADDASSIGN: |
| case PNK_SUBASSIGN: |
| case PNK_BITORASSIGN: |
| case PNK_BITXORASSIGN: |
| case PNK_BITANDASSIGN: |
| case PNK_LSHASSIGN: |
| case PNK_RSHASSIGN: |
| case PNK_URSHASSIGN: |
| case PNK_MULASSIGN: |
| case PNK_DIVASSIGN: |
| case PNK_MODASSIGN: |
| case PNK_POWASSIGN: |
| if (!emitAssignment(pn->pn_left, pn->getOp(), pn->pn_right)) |
| return false; |
| break; |
| |
| case PNK_CONDITIONAL: |
| if (!emitConditionalExpression(pn->as<ConditionalExpression>())) |
| return false; |
| break; |
| |
| case PNK_OR: |
| case PNK_AND: |
| if (!emitLogical(pn)) |
| return false; |
| break; |
| |
| case PNK_ADD: |
| case PNK_SUB: |
| case PNK_BITOR: |
| case PNK_BITXOR: |
| case PNK_BITAND: |
| case PNK_STRICTEQ: |
| case PNK_EQ: |
| case PNK_STRICTNE: |
| case PNK_NE: |
| case PNK_LT: |
| case PNK_LE: |
| case PNK_GT: |
| case PNK_GE: |
| case PNK_IN: |
| case PNK_INSTANCEOF: |
| case PNK_LSH: |
| case PNK_RSH: |
| case PNK_URSH: |
| case PNK_STAR: |
| case PNK_DIV: |
| case PNK_MOD: |
| if (!emitLeftAssociative(pn)) |
| return false; |
| break; |
| |
| case PNK_POW: |
| if (!emitRightAssociative(pn)) |
| return false; |
| break; |
| |
| case PNK_TYPEOFNAME: |
| if (!emitTypeof(pn, JSOP_TYPEOF)) |
| return false; |
| break; |
| |
| case PNK_TYPEOFEXPR: |
| if (!emitTypeof(pn, JSOP_TYPEOFEXPR)) |
| return false; |
| break; |
| |
| case PNK_THROW: |
| case PNK_VOID: |
| case PNK_NOT: |
| case PNK_BITNOT: |
| case PNK_POS: |
| case PNK_NEG: |
| if (!emitUnary(pn)) |
| return false; |
| break; |
| |
| case PNK_PREINCREMENT: |
| case PNK_PREDECREMENT: |
| case PNK_POSTINCREMENT: |
| case PNK_POSTDECREMENT: |
| if (!emitIncOrDec(pn)) |
| return false; |
| break; |
| |
| case PNK_DELETENAME: |
| if (!emitDeleteName(pn)) |
| return false; |
| break; |
| |
| case PNK_DELETEPROP: |
| if (!emitDeleteProperty(pn)) |
| return false; |
| break; |
| |
| case PNK_DELETEELEM: |
| if (!emitDeleteElement(pn)) |
| return false; |
| break; |
| |
| case PNK_DELETEEXPR: |
| if (!emitDeleteExpression(pn)) |
| return false; |
| break; |
| |
| case PNK_DOT: |
| if (pn->as<PropertyAccess>().isSuper()) { |
| if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) |
| return false; |
| } else { |
| if (!emitPropOp(pn, JSOP_GETPROP)) |
| return false; |
| } |
| break; |
| |
| case PNK_ELEM: |
| if (pn->as<PropertyByValue>().isSuper()) { |
| if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER)) |
| return false; |
| } else { |
| if (!emitElemOp(pn, JSOP_GETELEM)) |
| return false; |
| } |
| break; |
| |
| case PNK_NEW: |
| case PNK_TAGGED_TEMPLATE: |
| case PNK_CALL: |
| case PNK_GENEXP: |
| case PNK_SUPERCALL: |
| if (!emitCallOrNew(pn)) |
| return false; |
| break; |
| |
| case PNK_LEXICALSCOPE: |
| if (!emitLexicalScope(pn)) |
| return false; |
| break; |
| |
| case PNK_LETBLOCK: |
| if (!emitLetBlock(pn)) |
| return false; |
| break; |
| |
| case PNK_CONST: |
| case PNK_LET: |
| if (!emitVariables(pn, InitializeVars)) |
| return false; |
| break; |
| |
| case PNK_IMPORT: |
| MOZ_ASSERT(sc->isModuleBox()); |
| break; |
| |
| case PNK_EXPORT: |
| MOZ_ASSERT(sc->isModuleBox()); |
| if (pn->pn_kid->getKind() != PNK_EXPORT_SPEC_LIST) { |
| if (!emitTree(pn->pn_kid)) |
| return false; |
| } |
| break; |
| |
| case PNK_EXPORT_DEFAULT: |
| MOZ_ASSERT(sc->isModuleBox()); |
| if (!emitTree(pn->pn_kid)) |
| return false; |
| if (pn->pn_right) { |
| if (!emitLexicalInitialization(pn->pn_right, JSOP_DEFCONST)) |
| return false; |
| if (!emit1(JSOP_POP)) |
| return false; |
| } |
| break; |
| |
| case PNK_EXPORT_FROM: |
| MOZ_ASSERT(sc->isModuleBox()); |
| break; |
| |
| case PNK_ARRAYPUSH: |
| /* |
| * The array object's stack index is in arrayCompDepth. See below |
| * under the array initialiser code generator for array comprehension |
| * special casing. Note that the array object is a pure stack value, |
| * unaliased by blocks, so we can emitUnaliasedVarOp. |
| */ |
| if (!emitTree(pn->pn_kid)) |
| return false; |
| if (!emitDupAt(this->stackDepth - 1 - arrayCompDepth)) |
| return false; |
| if (!emit1(JSOP_ARRAYPUSH)) |
| return false; |
| break; |
| |
| case PNK_CALLSITEOBJ: |
| if (!emitCallSiteObject(pn)) |
| return false; |
| break; |
| |
| case PNK_ARRAY: |
| if (!emitArrayLiteral(pn)) |
| return false; |
| break; |
| |
| case PNK_ARRAYCOMP: |
| if (!emitArrayComp(pn)) |
| return false; |
| break; |
| |
| case PNK_OBJECT: |
| if (!emitObject(pn)) |
| return false; |
| break; |
| |
| case PNK_NAME: |
| if (!emitNameOp(pn, false)) |
| return false; |
| break; |
| |
| case PNK_TEMPLATE_STRING_LIST: |
| if (!emitTemplateString(pn)) |
| return false; |
| break; |
| |
| case PNK_TEMPLATE_STRING: |
| case PNK_STRING: |
| if (!emitAtomOp(pn, JSOP_STRING)) |
| return false; |
| break; |
| |
| case PNK_NUMBER: |
| if (!emitNumberOp(pn->pn_dval)) |
| return false; |
| break; |
| |
| case PNK_REGEXP: |
| if (!emitRegExp(regexpList.add(pn->as<RegExpLiteral>().objbox()))) |
| return false; |
| break; |
| |
| case PNK_TRUE: |
| case PNK_FALSE: |
| case PNK_NULL: |
| if (!emit1(pn->getOp())) |
| return false; |
| break; |
| |
| case PNK_THIS: |
| if (!emitThisLiteral(pn)) |
| return false; |
| break; |
| |
| case PNK_DEBUGGER: |
| if (!updateSourceCoordNotes(pn->pn_pos.begin)) |
| return false; |
| if (!emit1(JSOP_DEBUGGER)) |
| return false; |
| break; |
| |
| case PNK_NOP: |
| MOZ_ASSERT(pn->getArity() == PN_NULLARY); |
| break; |
| |
| case PNK_CLASS: |
| if (!emitClass(pn)) |
| return false; |
| break; |
| |
| case PNK_NEWTARGET: |
| if (!emit1(JSOP_NEWTARGET)) |
| return false; |
| break; |
| |
| case PNK_SETTHIS: |
| if (!emitSetThis(pn)) |
| return false; |
| break; |
| |
| case PNK_POSHOLDER: |
| MOZ_ASSERT_UNREACHABLE("Should never try to emit PNK_POSHOLDER"); |
| |
| default: |
| MOZ_ASSERT(0); |
| } |
| |
| /* bce->emitLevel == 1 means we're last on the stack, so finish up. */ |
| if (emitLevel == 1) { |
| if (!updateSourceCoordNotes(pn->pn_pos.end)) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) |
| { |
| // Start it off moderately large to avoid repeated resizings early on. |
| // ~99% of cases fit within 256 bytes. |
| if (notes.capacity() == 0 && !notes.reserve(256)) |
| return false; |
| |
| if (!notes.growBy(1)) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| *index = notes.length() - 1; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) |
| { |
| SrcNotesVector& notes = this->notes(); |
| unsigned index; |
| if (!AllocSrcNote(cx, notes, &index)) |
| return false; |
| |
| /* |
| * Compute delta from the last annotated bytecode's offset. If it's too |
| * big to fit in sn, allocate one or more xdelta notes and reset sn. |
| */ |
| ptrdiff_t offset = this->offset(); |
| ptrdiff_t delta = offset - lastNoteOffset(); |
| current->lastNoteOffset = offset; |
| if (delta >= SN_DELTA_LIMIT) { |
| do { |
| ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK); |
| SN_MAKE_XDELTA(¬es[index], xdelta); |
| delta -= xdelta; |
| if (!AllocSrcNote(cx, notes, &index)) |
| return false; |
| } while (delta >= SN_DELTA_LIMIT); |
| } |
| |
| /* |
| * Initialize type and delta, then allocate the minimum number of notes |
| * needed for type's arity. Usually, we won't need more, but if an offset |
| * does take two bytes, setSrcNoteOffset will grow notes. |
| */ |
| SN_MAKE_NOTE(¬es[index], type, delta); |
| for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) { |
| if (!newSrcNote(SRC_NULL)) |
| return false; |
| } |
| |
| if (indexp) |
| *indexp = index; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp) |
| { |
| unsigned index; |
| if (!newSrcNote(type, &index)) |
| return false; |
| if (!setSrcNoteOffset(index, 0, offset)) |
| return false; |
| if (indexp) |
| *indexp = index; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, |
| unsigned* indexp) |
| { |
| unsigned index; |
| if (!newSrcNote(type, &index)) |
| return false; |
| if (!setSrcNoteOffset(index, 0, offset1)) |
| return false; |
| if (!setSrcNoteOffset(index, 1, offset2)) |
| return false; |
| if (indexp) |
| *indexp = index; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta) |
| { |
| /* |
| * Called only from finishTakingSrcNotes to add to main script note |
| * deltas, and only by a small positive amount. |
| */ |
| MOZ_ASSERT(current == &main); |
| MOZ_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT); |
| |
| ptrdiff_t base = SN_DELTA(sn); |
| ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT; |
| ptrdiff_t newdelta = base + delta; |
| if (newdelta < limit) { |
| SN_SET_DELTA(sn, newdelta); |
| } else { |
| jssrcnote xdelta; |
| SN_MAKE_XDELTA(&xdelta, delta); |
| if (!main.notes.insert(sn, xdelta)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset) |
| { |
| if (!SN_REPRESENTABLE_OFFSET(offset)) { |
| ReportStatementTooLarge(parser->tokenStream, innermostStmt()); |
| return false; |
| } |
| |
| SrcNotesVector& notes = this->notes(); |
| |
| /* Find the offset numbered which (i.e., skip exactly which offsets). */ |
| jssrcnote* sn = ¬es[index]; |
| MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); |
| MOZ_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity); |
| for (sn++; which; sn++, which--) { |
| if (*sn & SN_4BYTE_OFFSET_FLAG) |
| sn += 3; |
| } |
| |
| /* |
| * See if the new offset requires four bytes either by being too big or if |
| * the offset has already been inflated (in which case, we need to stay big |
| * to not break the srcnote encoding if this isn't the last srcnote). |
| */ |
| if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) { |
| /* Maybe this offset was already set to a four-byte value. */ |
| if (!(*sn & SN_4BYTE_OFFSET_FLAG)) { |
| /* Insert three dummy bytes that will be overwritten shortly. */ |
| jssrcnote dummy = 0; |
| if (!(sn = notes.insert(sn, dummy)) || |
| !(sn = notes.insert(sn, dummy)) || |
| !(sn = notes.insert(sn, dummy))) |
| { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| } |
| *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24)); |
| *sn++ = (jssrcnote)(offset >> 16); |
| *sn++ = (jssrcnote)(offset >> 8); |
| } |
| *sn = (jssrcnote)offset; |
| return true; |
| } |
| |
| bool |
| BytecodeEmitter::finishTakingSrcNotes(uint32_t* out) |
| { |
| MOZ_ASSERT(current == &main); |
| |
| unsigned prologueCount = prologue.notes.length(); |
| if (prologueCount && prologue.currentLine != firstLine) { |
| switchToPrologue(); |
| if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(firstLine))) |
| return false; |
| switchToMain(); |
| } else { |
| /* |
| * Either no prologue srcnotes, or no line number change over prologue. |
| * We don't need a SRC_SETLINE, but we may need to adjust the offset |
| * of the first main note, by adding to its delta and possibly even |
| * prepending SRC_XDELTA notes to it to account for prologue bytecodes |
| * that came at and after the last annotated bytecode. |
| */ |
| ptrdiff_t offset = prologueOffset() - prologue.lastNoteOffset; |
| MOZ_ASSERT(offset >= 0); |
| if (offset > 0 && main.notes.length() != 0) { |
| /* NB: Use as much of the first main note's delta as we can. */ |
| jssrcnote* sn = main.notes.begin(); |
| ptrdiff_t delta = SN_IS_XDELTA(sn) |
| ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK) |
| : SN_DELTA_MASK - (*sn & SN_DELTA_MASK); |
| if (offset < delta) |
| delta = offset; |
| for (;;) { |
| if (!addToSrcNoteDelta(sn, delta)) |
| return false; |
| offset -= delta; |
| if (offset == 0) |
| break; |
| delta = Min(offset, SN_XDELTA_MASK); |
| sn = main.notes.begin(); |
| } |
| } |
| } |
| |
| // The prologue count might have changed, so we can't reuse prologueCount. |
| // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended |
| // when the notes are copied to their final destination by CopySrcNotes. |
| *out = prologue.notes.length() + main.notes.length() + 1; |
| return true; |
| } |
| |
| void |
| BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) |
| { |
| unsigned prologueCount = prologue.notes.length(); |
| unsigned mainCount = main.notes.length(); |
| unsigned totalCount = prologueCount + mainCount; |
| MOZ_ASSERT(totalCount == nsrcnotes - 1); |
| if (prologueCount) |
| PodCopy(destination, prologue.notes.begin(), prologueCount); |
| PodCopy(destination + prologueCount, main.notes.begin(), mainCount); |
| SN_MAKE_TERMINATOR(&destination[totalCount]); |
| } |
| |
| void |
| CGConstList::finish(ConstArray* array) |
| { |
| MOZ_ASSERT(length() == array->length); |
| |
| for (unsigned i = 0; i < length(); i++) |
| array->vector[i] = list[i]; |
| } |
| |
| /* |
| * Find the index of the given object for code generator. |
| * |
| * Since the emitter refers to each parsed object only once, for the index we |
| * use the number of already indexes objects. We also add the object to a list |
| * to convert the list to a fixed-size array when we complete code generation, |
| * see js::CGObjectList::finish below. |
| * |
| * Most of the objects go to BytecodeEmitter::objectList but for regexp we use |
| * a separated BytecodeEmitter::regexpList. In this way the emitted index can |
| * be directly used to store and fetch a reference to a cloned RegExp object |
| * that shares the same JSRegExp private data created for the object literal in |
| * objbox. We need a cloned object to hold lastIndex and other direct |
| * properties that should not be shared among threads sharing a precompiled |
| * function or script. |
| * |
| * If the code being compiled is function code, allocate a reserved slot in |
| * the cloned function object that shares its precompiled script with other |
| * cloned function objects and with the compiler-created clone-parent. There |
| * are nregexps = script->regexps()->length such reserved slots in each |
| * function object cloned from fun->object. NB: during compilation, a funobj |
| * slots element must never be allocated, because JSObject::allocSlot could |
| * hand out one of the slots that should be given to a regexp clone. |
| * |
| * If the code being compiled is global code, the cloned regexp are stored in |
| * fp->vars slot and to protect regexp slots from GC we set fp->nvars to |
| * nregexps. |
| * |
| * The slots initially contain undefined or null. We populate them lazily when |
| * JSOP_REGEXP is executed for the first time. |
| * |
| * Why clone regexp objects? ECMA specifies that when a regular expression |
| * literal is scanned, a RegExp object is created. In the spec, compilation |
| * and execution happen indivisibly, but in this implementation and many of |
| * its embeddings, code is precompiled early and re-executed in multiple |
| * threads, or using multiple global objects, or both, for efficiency. |
| * |
| * In such cases, naively following ECMA leads to wrongful sharing of RegExp |
| * objects, which makes for collisions on the lastIndex property (especially |
| * for global regexps) and on any ad-hoc properties. Also, __proto__ refers to |
| * the pre-compilation prototype, a pigeon-hole problem for instanceof tests. |
| */ |
| unsigned |
| CGObjectList::add(ObjectBox* objbox) |
| { |
| MOZ_ASSERT(!objbox->emitLink); |
| objbox->emitLink = lastbox; |
| lastbox = objbox; |
| return length++; |
| } |
| |
| unsigned |
| CGObjectList::indexOf(JSObject* obj) |
| { |
| MOZ_ASSERT(length > 0); |
| unsigned index = length - 1; |
| for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) |
| index--; |
| return index; |
| } |
| |
| void |
| CGObjectList::finish(ObjectArray* array) |
| { |
| MOZ_ASSERT(length <= INDEX_LIMIT); |
| MOZ_ASSERT(length == array->length); |
| |
| js::HeapPtrObject* cursor = array->vector + array->length; |
| ObjectBox* objbox = lastbox; |
| do { |
| --cursor; |
| MOZ_ASSERT(!*cursor); |
| MOZ_ASSERT(objbox->object->isTenured()); |
| *cursor = objbox->object; |
| } while ((objbox = objbox->emitLink) != nullptr); |
| MOZ_ASSERT(cursor == array->vector); |
| } |
| |
| ObjectBox* |
| CGObjectList::find(uint32_t index) |
| { |
| MOZ_ASSERT(index < length); |
| ObjectBox* box = lastbox; |
| for (unsigned n = length - 1; n > index; n--) |
| box = box->emitLink; |
| return box; |
| } |
| |
| bool |
| CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end) |
| { |
| MOZ_ASSERT(start <= end); |
| MOZ_ASSERT(size_t(uint32_t(start)) == start); |
| MOZ_ASSERT(size_t(uint32_t(end)) == end); |
| |
| JSTryNote note; |
| note.kind = kind; |
| note.stackDepth = stackDepth; |
| note.start = uint32_t(start); |
| note.length = uint32_t(end - start); |
| |
| return list.append(note); |
| } |
| |
| void |
| CGTryNoteList::finish(TryNoteArray* array) |
| { |
| MOZ_ASSERT(length() == array->length); |
| |
| for (unsigned i = 0; i < length(); i++) |
| array->vector[i] = list[i]; |
| } |
| |
| bool |
| CGBlockScopeList::append(uint32_t scopeObject, uint32_t offset, bool inPrologue, |
| uint32_t parent) |
| { |
| CGBlockScopeNote note; |
| mozilla::PodZero(¬e); |
| |
| note.index = scopeObject; |
| note.start = offset; |
| note.parent = parent; |
| note.startInPrologue = inPrologue; |
| |
| return list.append(note); |
| } |
| |
| uint32_t |
| CGBlockScopeList::findEnclosingScope(uint32_t index) |
| { |
| MOZ_ASSERT(index < length()); |
| MOZ_ASSERT(list[index].index != BlockScopeNote::NoBlockScopeIndex); |
| |
| DebugOnly<bool> inPrologue = list[index].startInPrologue; |
| DebugOnly<uint32_t> pos = list[index].start; |
| while (index--) { |
| MOZ_ASSERT_IF(inPrologue == list[index].startInPrologue, list[index].start <= pos); |
| if (list[index].end == 0) { |
| // We are looking for the nearest enclosing live scope. If the |
| // scope contains POS, it should still be open, so its length should |
| // be zero. |
| return list[index].index; |
| } |
| // Conversely, if the length is not zero, it should not contain POS. |
| MOZ_ASSERT_IF(inPrologue == list[index].endInPrologue, list[index].end <= pos); |
| } |
| |
| return BlockScopeNote::NoBlockScopeIndex; |
| } |
| |
| void |
| CGBlockScopeList::recordEnd(uint32_t index, uint32_t offset, bool inPrologue) |
| { |
| MOZ_ASSERT(index < length()); |
| MOZ_ASSERT(offset >= list[index].start); |
| MOZ_ASSERT(list[index].length == 0); |
| list[index].end = offset; |
| list[index].endInPrologue = inPrologue; |
| } |
| |
| void |
| CGBlockScopeList::finish(BlockScopeArray* array, uint32_t prologueLength) |
| { |
| MOZ_ASSERT(length() == array->length); |
| |
| for (unsigned i = 0; i < length(); i++) { |
| if (!list[i].startInPrologue) |
| list[i].start += prologueLength; |
| if (!list[i].endInPrologue) |
| list[i].end += prologueLength; |
| list[i].length = list[i].end - list[i].start; |
| array->vector[i] = list[i]; |
| } |
| } |
| |
| void |
| CGYieldOffsetList::finish(YieldOffsetArray& array, uint32_t prologueLength) |
| { |
| MOZ_ASSERT(length() == array.length()); |
| |
| for (unsigned i = 0; i < length(); i++) |
| array[i] = prologueLength + list[i]; |
| } |
| |
| /* |
| * We should try to get rid of offsetBias (always 0 or 1, where 1 is |
| * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR. |
| */ |
| const JSSrcNoteSpec js_SrcNoteSpec[] = { |
| #define DEFINE_SRC_NOTE_SPEC(sym, name, arity) { name, arity }, |
| FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC) |
| #undef DEFINE_SRC_NOTE_SPEC |
| }; |
| |
| static int |
| SrcNoteArity(jssrcnote* sn) |
| { |
| MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST); |
| return js_SrcNoteSpec[SN_TYPE(sn)].arity; |
| } |
| |
| JS_FRIEND_API(unsigned) |
| js::SrcNoteLength(jssrcnote* sn) |
| { |
| unsigned arity; |
| jssrcnote* base; |
| |
| arity = SrcNoteArity(sn); |
| for (base = sn++; arity; sn++, arity--) { |
| if (*sn & SN_4BYTE_OFFSET_FLAG) |
| sn += 3; |
| } |
| return sn - base; |
| } |
| |
| JS_FRIEND_API(ptrdiff_t) |
| js::GetSrcNoteOffset(jssrcnote* sn, unsigned which) |
| { |
| /* Find the offset numbered which (i.e., skip exactly which offsets). */ |
| MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); |
| MOZ_ASSERT((int) which < SrcNoteArity(sn)); |
| for (sn++; which; sn++, which--) { |
| if (*sn & SN_4BYTE_OFFSET_FLAG) |
| sn += 3; |
| } |
| if (*sn & SN_4BYTE_OFFSET_FLAG) { |
| return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) |
| | (sn[1] << 16) |
| | (sn[2] << 8) |
| | sn[3]); |
| } |
| return (ptrdiff_t)*sn; |
| } |