diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1,9362 +1,9387 @@ /* -*- 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/ReverseIterator.h" #include #include "jsnum.h" #include "jstypes.h" #include "jsutil.h" #include "ds/Nestable.h" #include "frontend/BytecodeControlStructures.h" #include "frontend/CallOrNewEmitter.h" #include "frontend/CForEmitter.h" #include "frontend/DoWhileEmitter.h" #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ExpressionStatementEmitter.h" #include "frontend/ForInEmitter.h" #include "frontend/ForOfEmitter.h" #include "frontend/ForOfLoopControl.h" #include "frontend/IfEmitter.h" #include "frontend/NameOpEmitter.h" #include "frontend/ParseNode.h" #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" #include "frontend/SwitchEmitter.h" #include "frontend/TDZCheckCache.h" #include "frontend/TryEmitter.h" #include "frontend/WhileEmitter.h" #include "js/CompileOptions.h" #include "vm/BytecodeUtil.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSScript.h" #include "vm/Stack.h" #include "wasm/AsmJS.h" #include "vm/JSObject-inl.h" using namespace js; using namespace js::frontend; using mozilla::AssertedCast; using mozilla::DebugOnly; using mozilla::Maybe; using mozilla::Nothing; using mozilla::NumberEqualsInt32; using mozilla::NumberIsInt32; using mozilla::PodCopy; using mozilla::Some; using mozilla::Unused; static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { // The few node types listed below are exceptions to the usual // location-source-note-emitting code in BytecodeEmitter::emitTree(). // Single-line `while` loops and C-style `for` loops require careful // handling to avoid strange stepping behavior. // Functions usually shouldn't have location information (bug 1431202). ParseNodeKind kind = pn->getKind(); return kind == ParseNodeKind::While || kind == ParseNodeKind::For || kind == ParseNodeKind::Function; } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, HandleScript script, Handle lazyScript, 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(nullptr), atomIndices(cx->frontendCollectionPool()), firstLine(lineNum), maxFixedSlots(0), maxStackDepth(0), stackDepth(0), emitLevel(0), bodyScopeIndex(UINT32_MAX), varEmitterScope(nullptr), innermostNestableControl(nullptr), innermostEmitterScope_(nullptr), innermostTDZCheckCache(nullptr), #ifdef DEBUG unstableEmitterScope(false), #endif numberList(cx), scopeList(cx), tryNoteList(cx), scopeNoteList(cx), resumeOffsetList(cx), typesetCount(0), hasSingletons(false), hasTryFinally(false), emittingRunOnceLambda(false), emitterMode(emitterMode), scriptStartOffsetSet(false), functionBodyEndPosSet(false) { MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* handle, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode) : BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode) { parser = handle; } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode) : BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode) { ep_.emplace(parser); this->parser = ep_.ptr(); } void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { setScriptStartOffsetIfUnset(bodyPosition); setFunctionBodyEndPos(bodyPosition); } bool BytecodeEmitter::init() { return atomIndices.acquire(cx); } template T* BytecodeEmitter::findInnermostNestableControl() const { return NestableControl::findNearest(innermostNestableControl); } template bool */> T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { return NestableControl::findNearest(innermostNestableControl, predicate); } NameLocation BytecodeEmitter::lookupName(JSAtom* name) { return innermostEmitterScope()->lookup(this, name); } Maybe BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) { return innermostEmitterScope()->locationBoundInScope(name, target); } Maybe BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source) { EmitterScope* funScope = source; while (!funScope->scope(this)->is()) { funScope = funScope->enclosingInFrame(); } return source->locationBoundInScope(name, funScope); } bool BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset) { *offset = code().length(); if (!code().growBy(delta)) { ReportOutOfMemory(cx); return false; } return true; } void BytecodeEmitter::updateDepth(ptrdiff_t target) { jsbytecode* pc = code(target); int nuses = StackUses(pc); int ndefs = StackDefs(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::emitJumpTarget(JumpTarget* target) { ptrdiff_t off = offset(); // Alias consecutive jump targets. if (off == current->lastTarget.offset + ptrdiff_t(JSOP_JUMPTARGET_LENGTH)) { target->offset = current->lastTarget.offset; return true; } target->offset = off; current->lastTarget.offset = off; if (!emit1(JSOP_JUMPTARGET)) { return false; } return true; } bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { ptrdiff_t offset; if (!emitCheck(5, &offset)) { return false; } jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); MOZ_ASSERT(-1 <= jump->offset && jump->offset < offset); jump->push(this->code(0), offset); updateDepth(offset); return true; } bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) { if (!emitJumpNoFallthrough(op, jump)) { return false; } if (BytecodeFallsThrough(op)) { JumpTarget fallthrough; if (!emitJumpTarget(&fallthrough)) { return false; } } return true; } bool BytecodeEmitter::emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump, JumpTarget* fallthrough) { if (!emitJumpNoFallthrough(op, jump)) { return false; } patchJumpsToTarget(*jump, target); // Unconditionally create a fallthrough for closing iterators, and as a // target for break statements. if (!emitJumpTarget(fallthrough)) { return false; } return true; } void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { MOZ_ASSERT(-1 <= jump.offset && jump.offset <= offset()); MOZ_ASSERT(0 <= target.offset && target.offset <= offset()); MOZ_ASSERT_IF(jump.offset != -1 && target.offset + 4 <= offset(), BytecodeIsJumpTarget(JSOp(*code(target.offset)))); jump.patchAll(code(0), target); } bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { if (jump.offset == -1) { return true; } JumpTarget target; if (!emitJumpTarget(&target)) { return false; } patchJumpsToTarget(jump, target); return true; } bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, const Maybe& sourceCoordOffset) { if (sourceCoordOffset.isSome()) { if (!updateSourceCoordNotes(*sourceCoordOffset)) { return false; } } return emit3(op, ARGC_LO(argc), ARGC_HI(argc)); } bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) { return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); } bool BytecodeEmitter::emitDupAt(unsigned slotFromTop) { MOZ_ASSERT(slotFromTop < unsigned(stackDepth)); if (slotFromTop == 0) { return emit1(JSOP_DUP); } 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; } bool BytecodeEmitter::emitPopN(unsigned n) { MOZ_ASSERT(n != 0); if (n == 1) { return emit1(JSOP_POP); } // 2 JSOP_POPs (2 bytes) are shorter than JSOP_POPN (3 bytes). if (n == 2) { return emit1(JSOP_POP) && emit1(JSOP_POP); } return emitUint16Operand(JSOP_POPN, n); } bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } bool BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) { return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind)); } static inline unsigned LengthOfSetLine(unsigned line) { return 1 /* SRC_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); } /* Updates line number notes, not column notes. */ bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { ErrorReporter* er = &parser->errorReporter(); bool onThisLine; if (!er->isOnThisLine(offset, currentLine(), &onThisLine)) { er->reportErrorNoOffset(JSMSG_OUT_OF_MEMORY); return false; } if (!onThisLine) { unsigned line = er->lineAt(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->errorReporter().columnAt(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; } Maybe BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { if (!nextpn) { return Nothing(); } // Try to give the JSOP_LOOPHEAD and JSOP_LOOPENTRY 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. if (nextpn->is()) { nextpn = nextpn->as().scopeBody(); } if (nextpn->isKind(ParseNodeKind::StatementList)) { if (ParseNode* firstStatement = nextpn->as().head()) { nextpn = firstStatement; } } return Some(nextpn->pn_pos.begin); } 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_LO(operand), UINT16_HI(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; } namespace { class NonLocalExitControl { public: enum Kind { // IteratorClose is handled especially inside the exception unwinder. Throw, // A 'continue' statement does not call IteratorClose for the loop it // is continuing, i.e. excluding the target loop. Continue, // A 'break' or 'return' statement does call IteratorClose for the // loop it is breaking out of or returning from, i.e. including the // target loop. Break, Return }; private: BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; Kind kind_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(EmitterScope* scope); public: NonLocalExitControl(BytecodeEmitter* bce, Kind kind) : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), kind_(kind) { } ~NonLocalExitControl() { for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length(); n++) { bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue()); } bce_->stackDepth = savedDepth_; } MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target); MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { return prepareForNonLocalJump(nullptr); } }; bool NonLocalExitControl::leaveScope(EmitterScope* es) { if (!es->leave(bce_, /* nonLocal = */ true)) { return false; } // As we pop each scope due to the non-local jump, emit notes that // record the extent of the enclosing scope. These notes will have // their ends recorded in ~NonLocalExitControl(). uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex; if (es->enclosingInFrame()) { enclosingScopeIndex = es->enclosingInFrame()->index(); } if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(), bce_->inPrologue(), openScopeNoteIndex_)) { return false; } openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1; return true; } /* * Emit additional bytecode(s) for non-local jumps. */ bool NonLocalExitControl::prepareForNonLocalJump(NestableControl* target) { EmitterScope* es = bce_->innermostEmitterScope(); int npops = 0; AutoCheckUnstableEmitterScope cues(bce_); // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->emitPopN(npops)) { return false; } npops = 0; return true; }; // Walk the nestable control stack and patch jumps. for (NestableControl* control = bce_->innermostNestableControl; control != target; control = control->enclosing()) { // Walk the scope stack and leave the scopes we entered. Leaving a scope // may emit administrative ops like JSOP_POPLEXICALENV but never anything // that manipulates the stack. for (; es != control->emitterScope(); es = es->enclosingInFrame()) { if (!leaveScope(es)) { return false; } } switch (control->kind()) { case StatementKind::Finally: { TryFinallyControl& finallyControl = control->as(); if (finallyControl.emittingSubroutine()) { /* * 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; } else { if (!flushPops(bce_)) { return false; } - if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) { // ... + if (!bce_->emitGoSub(&finallyControl.gosubs)) { // ... return false; } } break; } case StatementKind::ForOfLoop: if (emitIteratorClose) { if (!flushPops(bce_)) { return false; } ForOfLoopControl& loopinfo = control->as(); if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, /* isTarget = */ false)) { // ... return false; } } else { // The iterator next method, the iterator, and the current // value are on the stack. npops += 3; } break; case StatementKind::ForInLoop: if (!flushPops(bce_)) { return false; } // The iterator and the current value are on the stack. if (!bce_->emit1(JSOP_POP)) { // ... ITER return false; } if (!bce_->emit1(JSOP_ENDITER)) { // ... return false; } break; default: break; } } if (!flushPops(bce_)) { return false; } if (target && emitIteratorCloseAtTarget && target->is()) { ForOfLoopControl& loopinfo = target->as(); if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, /* isTarget = */ true)) { // ... UNDEF UNDEF UNDEF return false; } } EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) { return false; } } return true; } } // anonymous namespace bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { NonLocalExitControl nle(this, noteType == SRC_CONTINUE ? NonLocalExitControl::Continue : NonLocalExitControl::Break); if (!nle.prepareForNonLocalJump(target)) { return false; } if (noteType != SRC_NULL) { if (!newSrcNote(noteType)) { return false; } } return emitJump(JSOP_GOTO, jumplist); } Scope* BytecodeEmitter::innermostScope() const { return innermostEmitterScope()->scope(this); } 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); checkTypeSet(op); updateDepth(offset); 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); checkTypeSet(op); updateDepth(offset); return true; } bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) { MOZ_ASSERT(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, atom != cx->names().dotGenerator); if (op == JSOP_GETPROP && atom == cx->names().length) { /* Specialize length accesses for the interpreter. */ op = JSOP_LENGTH; } uint32_t index; if (!makeAtomIndex(atom, &index)) { return false; } return emitAtomOp(index, op); } bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); return emitIndexOp(op, atomIndex); } bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); MOZ_ASSERT(index < scopeList.length()); return emitIndex32(op, index); } 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_ENVCOORD); MOZ_ASSERT(IsLocalOp(op)); ptrdiff_t off; if (!emitN(op, LOCALNO_LEN, &off)) { return false; } SET_LOCALNO(code(off), slot); return true; } bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t 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::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD); unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_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_ENVCOORD_HOPS(pc, ec.hops()); pc += ENVCOORD_HOPS_LEN; SET_ENVCOORD_SLOT(pc, ec.slot()); pc += ENVCOORD_SLOT_LEN; checkTypeSet(op); 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; } bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) { if (!CheckRecursionLimit(cx)) { return false; } restart: switch (pn->getKind()) { // Trivial cases with no side effects. case ParseNodeKind::EmptyStatement: case ParseNodeKind::True: case ParseNodeKind::False: case ParseNodeKind::Null: case ParseNodeKind::RawUndefined: case ParseNodeKind::Elision: case ParseNodeKind::Generator: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::ObjectPropertyName: case ParseNodeKind::PrivateName: // no side effects, unlike ParseNodeKind::Name case ParseNodeKind::String: case ParseNodeKind::TemplateString: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::RegExp: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::Number: MOZ_ASSERT(pn->is()); *answer = false; return true; // |this| can throw in derived class constructors, including nested arrow // functions or eval. case ParseNodeKind::This: MOZ_ASSERT(pn->is()); *answer = sc->needsThisTDZChecks(); return true; // Trivial binary nodes with more token pos holders. case ParseNodeKind::NewTarget: case ParseNodeKind::ImportMeta: { MOZ_ASSERT(pn->as().left()->isKind(ParseNodeKind::PosHolder)); MOZ_ASSERT(pn->as().right()->isKind(ParseNodeKind::PosHolder)); *answer = false; return true; } case ParseNodeKind::Break: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Continue: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Debugger: MOZ_ASSERT(pn->is()); *answer = true; return true; // Watch out for getters! case ParseNodeKind::Dot: MOZ_ASSERT(pn->is()); *answer = true; return true; // Unary cases with side effects only if the child has them. case ParseNodeKind::TypeOfExpr: case ParseNodeKind::Void: case ParseNodeKind::Not: return checkSideEffects(pn->as().kid(), answer); // Even if the name expression is effect-free, performing ToPropertyKey on // it might not be effect-free: // // RegExp.prototype.toString = () => { throw 42; }; // ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42 // // function Q() { // ({ [new.target]: 0 }); // } // Q.toString = () => { throw 17; }; // new Q; // new.target will be Q, ToPropertyKey(Q) throws 17 case ParseNodeKind::ComputedName: MOZ_ASSERT(pn->is()); *answer = true; return true; // Looking up or evaluating the associated name could throw. case ParseNodeKind::TypeOfName: MOZ_ASSERT(pn->is()); *answer = true; return true; // This unary case has side effects on the enclosing object, 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, because an object literal having a mutated prototype only // produces a value, without affecting anything else. case ParseNodeKind::MutateProto: return checkSideEffects(pn->as().kid(), answer); // Unary cases with obvious side effects. case ParseNodeKind::PreIncrement: case ParseNodeKind::PostIncrement: case ParseNodeKind::PreDecrement: case ParseNodeKind::PostDecrement: case ParseNodeKind::Throw: MOZ_ASSERT(pn->is()); *answer = true; return true; // These might invoke valueOf/toString, even with a subexpression without // side effects! Consider |+{ valueOf: null, toString: null }|. case ParseNodeKind::BitNot: case ParseNodeKind::Pos: case ParseNodeKind::Neg: MOZ_ASSERT(pn->is()); *answer = true; return true; // This invokes the (user-controllable) iterator protocol. case ParseNodeKind::Spread: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::InitialYield: case ParseNodeKind::YieldStar: case ParseNodeKind::Yield: case ParseNodeKind::Await: MOZ_ASSERT(pn->is()); *answer = true; return true; // Deletion generally has side effects, even if isolated cases have none. case ParseNodeKind::DeleteName: case ParseNodeKind::DeleteProp: case ParseNodeKind::DeleteElem: MOZ_ASSERT(pn->is()); *answer = true; return true; // Deletion of a non-Reference expression has side effects only through // evaluating the expression. case ParseNodeKind::DeleteExpr: { ParseNode* expr = pn->as().kid(); return checkSideEffects(expr, answer); } case ParseNodeKind::ExpressionStatement: return checkSideEffects(pn->as().kid(), answer); // Binary cases with obvious side effects. case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitXorAssign: case ParseNodeKind::BitAndAssign: case ParseNodeKind::LshAssign: case ParseNodeKind::RshAssign: case ParseNodeKind::UrshAssign: case ParseNodeKind::MulAssign: case ParseNodeKind::DivAssign: case ParseNodeKind::ModAssign: case ParseNodeKind::PowAssign: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::SetThis: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::StatementList: // Strict equality operations and logical operators are well-behaved and // perform no conversions. case ParseNodeKind::Or: case ParseNodeKind::And: case ParseNodeKind::StrictEq: case ParseNodeKind::StrictNe: // Any subexpression of a comma expression could be effectful. case ParseNodeKind::Comma: MOZ_ASSERT(!pn->as().empty()); MOZ_FALLTHROUGH; // Subcomponents of a literal may be effectful. case ParseNodeKind::Array: case ParseNodeKind::Object: for (ParseNode* item : pn->as().contents()) { 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 ParseNodeKind::BitOr: case ParseNodeKind::BitXor: case ParseNodeKind::BitAnd: case ParseNodeKind::Eq: case ParseNodeKind::Ne: case ParseNodeKind::Lt: case ParseNodeKind::Le: case ParseNodeKind::Gt: case ParseNodeKind::Ge: case ParseNodeKind::InstanceOf: case ParseNodeKind::In: case ParseNodeKind::Lsh: case ParseNodeKind::Rsh: case ParseNodeKind::Ursh: case ParseNodeKind::Add: case ParseNodeKind::Sub: case ParseNodeKind::Star: case ParseNodeKind::Div: case ParseNodeKind::Mod: case ParseNodeKind::Pow: MOZ_ASSERT(pn->as().count() >= 2); *answer = true; return true; case ParseNodeKind::Colon: case ParseNodeKind::Case: { BinaryNode* node = &pn->as(); if (!checkSideEffects(node->left(), answer)) { return false; } if (*answer) { return true; } return checkSideEffects(node->right(), answer); } // More getters. case ParseNodeKind::Elem: MOZ_ASSERT(pn->is()); *answer = true; return true; // These affect visible names in this code, or in other code. case ParseNodeKind::Import: case ParseNodeKind::ExportFrom: case ParseNodeKind::ExportDefault: MOZ_ASSERT(pn->is()); *answer = true; return true; // Likewise. case ParseNodeKind::Export: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::CallImport: MOZ_ASSERT(pn->is()); *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 ParseNodeKind::DoWhile: case ParseNodeKind::While: case ParseNodeKind::For: MOZ_ASSERT(pn->is()); *answer = true; return true; // Declarations affect the name set of the relevant scope. case ParseNodeKind::Var: case ParseNodeKind::Const: case ParseNodeKind::Let: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::If: case ParseNodeKind::Conditional: { TernaryNode* node = &pn->as(); if (!checkSideEffects(node->kid1(), answer)) { return false; } if (*answer) { return true; } if (!checkSideEffects(node->kid2(), answer)) { return false; } if (*answer) { return true; } if ((pn = node->kid3())) { goto restart; } return true; } // Function calls can invoke non-local code. case ParseNodeKind::New: case ParseNodeKind::Call: case ParseNodeKind::TaggedTemplate: case ParseNodeKind::SuperCall: MOZ_ASSERT(pn->is()); *answer = true; return true; // Function arg lists can contain arbitrary expressions. Technically // this only causes side-effects if one of the arguments does, but since // the call being made will always trigger side-effects, it isn't needed. case ParseNodeKind::Arguments: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Pipeline: MOZ_ASSERT(pn->as().count() >= 2); *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 ParseNodeKind::Class: MOZ_ASSERT(pn->is()); *answer = true; return true; // |with| calls |ToObject| on its expression and so throws if that value // is null/undefined. case ParseNodeKind::With: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Return: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Name: MOZ_ASSERT(pn->is()); *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 ParseNodeKind::Shorthand: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Function: MOZ_ASSERT(pn->is()); /* * 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 ParseNodeKind::Module: *answer = false; return true; case ParseNodeKind::Try: { TryNode* tryNode = &pn->as(); if (!checkSideEffects(tryNode->body(), answer)) { return false; } if (*answer) { return true; } if (LexicalScopeNode* catchScope = tryNode->catchScope()) { if (!checkSideEffects(catchScope, answer)) { return false; } if (*answer) { return true; } } if (ParseNode* finallyBlock = tryNode->finallyBlock()) { if (!checkSideEffects(finallyBlock, answer)) { return false; } } return true; } case ParseNodeKind::Catch: { BinaryNode* catchClause = &pn->as(); if (ParseNode* name = catchClause->left()) { if (!checkSideEffects(name, answer)) { return false; } if (*answer) { return true; } } return checkSideEffects(catchClause->right(), answer); } case ParseNodeKind::Switch: { SwitchStatement* switchStmt = &pn->as(); if (!checkSideEffects(&switchStmt->discriminant(), answer)) { return false; } return *answer || checkSideEffects(&switchStmt->lexicalForCaseList(), answer); } case ParseNodeKind::Label: return checkSideEffects(pn->as().statement(), answer); case ParseNodeKind::LexicalScope: return checkSideEffects(pn->as().scopeBody(), 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 ParseNodeKind::TemplateStringList: { ListNode* list = &pn->as(); MOZ_ASSERT(!list->empty()); MOZ_ASSERT((list->count() % 2) == 1, "template strings must alternate template and substitution " "parts"); *answer = list->count() > 1; return true; } // This should be unreachable but is left as-is for now. case ParseNodeKind::ParamsBody: *answer = true; return true; case ParseNodeKind::ForIn: // by ParseNodeKind::For case ParseNodeKind::ForOf: // by ParseNodeKind::For case ParseNodeKind::ForHead: // by ParseNodeKind::For case ParseNodeKind::ClassMethod: // by ParseNodeKind::Class case ParseNodeKind::ClassField: // by ParseNodeKind::Class case ParseNodeKind::ClassNames: // by ParseNodeKind::Class case ParseNodeKind::ClassMemberList: // by ParseNodeKind::Class case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import case ParseNodeKind::ExportBatchSpec: // by ParseNodeKind::Export case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others case ParseNodeKind::PropertyName: // by ParseNodeKind::Dot MOZ_CRASH("handled by parent nodes"); case ParseNodeKind::Limit: // invalid sentinel value MOZ_CRASH("invalid node kind"); } MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in " "BytecodeEmitter::checkSideEffects"); } bool BytecodeEmitter::isInLoop() { return findInnermostNestableControl(); } 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' scope. if (sc->inWith()) { return true; } // Otherwise see if the current point is under a 'with'. for (EmitterScope* es = innermostEmitterScope(); es; es = es->enclosingInFrame()) { if (es->scope(this)->kind() == ScopeKind::With) { return true; } } return false; } void BytecodeEmitter::tellDebuggerAboutCompiledScript(JSContext* cx) { // Note: when parsing off thread the resulting scripts need to be handed to // the debugger after rejoining to the main thread. if (cx->helperThread()) { 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, script); } } void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { MOZ_ASSERT_IF(!pn, this->scriptStartOffsetSet); uint32_t offset = pn ? pn->pn_pos.begin : this->scriptStartOffset; va_list args; va_start(args, errorNumber); parser->errorReporter().errorAtVA(offset, errorNumber, &args); va_end(args); } void BytecodeEmitter::reportError(const Maybe& maybeOffset, unsigned errorNumber, ...) { MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet); uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset; va_list args; va_start(args, errorNumber); parser->errorReporter().errorAtVA(offset, errorNumber, &args); va_end(args); } bool BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...) { MOZ_ASSERT_IF(!pn, this->scriptStartOffsetSet); uint32_t offset = pn ? pn->pn_pos.begin : this->scriptStartOffset; va_list args; va_start(args, errorNumber); bool result = parser->errorReporter().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, &args); va_end(args); return result; } bool BytecodeEmitter::reportExtraWarning(const Maybe& maybeOffset, unsigned errorNumber, ...) { MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet); uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset; va_list args; va_start(args, errorNumber); bool result = parser->errorReporter().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, &args); va_end(args); return result; } bool BytecodeEmitter::emitNewInit() { 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] = 0; code[2] = 0; code[3] = 0; code[4] = 0; checkTypeSet(JSOP_NEWINIT); updateDepth(offset); 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(cx, kind, TenuredObject)); if (!obj) { return false; } Rooted value_id(cx, NameToId(cx->names().value)); Rooted done_id(cx, NameToId(cx->names().done)); if (!NativeDefineDataProperty(cx, obj, value_id, UndefinedHandleValue, JSPROP_ENUMERATE)) { return false; } if (!NativeDefineDataProperty(cx, obj, done_id, UndefinedHandleValue, 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) { uint32_t value_id; if (!makeAtomIndex(cx->names().value, &value_id)) { return false; } uint32_t 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::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc) { NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); if (!noe.emitGet()) { return false; } return true; } bool BytecodeEmitter::emitGetName(NameNode* name) { return emitGetName(name->name()); } bool BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc) { // Dynamic accesses have TDZ checks built into their VM code and should // never emit explicit TDZ checks. MOZ_ASSERT(loc.hasKnownSlot()); MOZ_ASSERT(loc.isLexical()); Maybe check = innermostTDZCheckCache->needsTDZCheck(this, name); if (!check) { return false; } // We've already emitted a check in this basic block. if (*check == DontCheckTDZ) { return true; } if (loc.kind() == NameLocation::Kind::FrameSlot) { if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot())) { return false; } } else { if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate())) { return false; } } return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ); } bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) { MOZ_ASSERT(!prop->isSuper()); ParseNode* expr = &prop->expression(); if (!expr->is() || expr->as().isSuper()) { // The non-optimized case. return emitTree(expr); } // If the object operand is also a dotted property reference, reverse the // list linked via expression() temporarily so we can iterate over it from // the bottom up (reversing again as we go), to avoid excessive recursion. PropertyAccess* pndot = &expr->as(); ParseNode* pnup = nullptr; ParseNode* pndown; for (;;) { // Reverse pndot->expression() to point up, not down. pndown = &pndot->expression(); pndot->setExpression(pnup); if (!pndown->is() || pndown->as().isSuper()) { break; } pnup = pndot; pndot = &pndown->as(); } // pndown is a primary expression, not a dotted property reference. if (!emitTree(pndown)) { return false; } while (true) { // TODO(khyperia): Implement private field access. // Walk back up the list, emitting annotated name ops. if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) { return false; } // Reverse the pndot->expression() link again. pnup = pndot->maybeExpression(); pndot->setExpression(pndown); pndown = pndot; if (!pnup) { break; } pndot = &pnup->as(); } return true; } bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec) { PropertyAccess* prop = &incDec->kid()->as(); // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); ParseNodeKind kind = incDec->getKind(); PropOpEmitter poe(this, kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement : PropOpEmitter::Kind::PreDecrement, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } } else { if (!emitPropLHS(prop)) { return false; // OBJ } } if (!poe.emitIncDec(prop->key().atom())) { // RESULT return false; } return true; } bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) { MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); ParseNodeKind kind = incDec->getKind(); NameNode* name = &incDec->kid()->as(); NameOpEmitter noe(this, name->atom(), kind == ParseNodeKind::PostIncrement ? NameOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrement ? NameOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrement ? NameOpEmitter::Kind::PostDecrement : NameOpEmitter::Kind::PreDecrement); if (!noe.emitIncDec()) { return false; } return true; } bool BytecodeEmitter::emitElemOpBase(JSOp op) { if (!emit1(op)) { return false; } checkTypeSet(op); return true; } bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe) { if (isSuper) { if (!eoe.prepareForObj()) { // return false; } UnaryNode* base = &elem->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } if (!eoe.prepareForKey()) { // THIS return false; } if (!emitTree(&elem->key())) { // THIS KEY return false; } return true; } if (!eoe.prepareForObj()) { // return false; } if (!emitTree(&elem->expression())) { // OBJ return false; } if (!eoe.prepareForKey()) { // OBJ? OBJ return false; } if (!emitTree(&elem->key())) { // OBJ? OBJ KEY return false; } return true; } bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec) { PropertyByValue* elemExpr = &incDec->kid()->as(); bool isSuper = elemExpr->isSuper(); ParseNodeKind kind = incDec->getKind(); ElemOpEmitter eoe(this, kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement : ElemOpEmitter::Kind::PreDecrement, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { // [Super] // // THIS KEY // // [Other] // // OBJ KEY return false; } if (!eoe.emitIncDec()) { // RESULT return false; } return true; } bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) { MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrement) || incDec->isKind(ParseNodeKind::PostIncrement) || incDec->isKind(ParseNodeKind::PreDecrement) || incDec->isKind(ParseNodeKind::PostDecrement)); ParseNode* call = incDec->kid(); MOZ_ASSERT(call->isKind(ParseNodeKind::Call)); if (!emitTree(call)) { // CALLRESULT return false; } if (!emit1(JSOP_POS)) { // N return false; } // The increment/decrement has no side effects, so proceed to throw for // invalid assignment target. return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS); } 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 (!numberList.append(dval)) { return false; } return emitIndex32(JSOP_DOUBLE, numberList.length() - 1); } /* * 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(SwitchStatement* switchStmt) { LexicalScopeNode& lexical = switchStmt->lexicalForCaseList(); MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope)); ListNode* cases = &lexical.scopeBody()->as(); MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); SwitchEmitter se(this); if (!se.emitDiscriminant(Some(switchStmt->pn_pos.begin))) { return false; } if (!emitTree(&switchStmt->discriminant())) { return false; } // Enter the scope before pushing the switch BreakableControl since all // breaks are under this scope. if (!lexical.isEmptyScope()) { if (!se.emitLexical(lexical.scopeBindings())) { return false; } // A switch statement may contain hoisted functions inside its // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST // bodies of the cases to the case list. if (cases->hasTopLevelFunctionDeclarations()) { for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); ListNode* statements = caseClause->statementList(); if (statements->hasTopLevelFunctionDeclarations()) { if (!emitHoistedFunctionsInList(statements)) { return false; } } } } } else { MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations()); } SwitchEmitter::TableGenerator tableGen(this); uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0); if (caseCount == 0) { tableGen.finish(0); } else { for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { continue; } ParseNode* caseValue = caseClause->caseExpression(); if (caseValue->getKind() != ParseNodeKind::Number) { tableGen.setInvalid(); break; } int32_t i; if (!NumberEqualsInt32(caseValue->as().value(), &i)) { tableGen.setInvalid(); break; } if (!tableGen.addNumber(i)) { return false; } } tableGen.finish(caseCount); } if (!se.validateCaseCount(caseCount)) { return false; } bool isTableSwitch = tableGen.isValid(); if (isTableSwitch) { if (!se.emitTable(tableGen)) { return false; } } else { if (!se.emitCond()) { return false; } // Emit code for evaluating cases and jumping to case statements. for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { continue; } if (!se.prepareForCaseValue()) { return false; } ParseNode* caseValue = caseClause->caseExpression(); // If the expression is a literal, suppress line number emission so // that debugging works more naturally. if (!emitTree(caseValue, ValueUsage::WantValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { return false; } if (!se.emitCaseJump()) { return false; } } } // Emit code for each case's statements. for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { if (!se.emitDefaultBody()) { return false; } } else { if (isTableSwitch) { ParseNode* caseValue = caseClause->caseExpression(); MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number)); NumericLiteral* literal = &caseValue->as(); #ifdef DEBUG // Use NumberEqualsInt32 here because switches compare using // strict equality, which will equate -0 and +0. In contrast // NumberIsInt32 would return false for -0. int32_t v; MOZ_ASSERT(mozilla::NumberEqualsInt32(literal->value(), &v)); #endif int32_t i = int32_t(literal->value()); if (!se.emitCaseBody(i, tableGen)) { return false; } } else { if (!se.emitCaseBody()) { return false; } } } if (!emitTree(caseClause->statementList())) { return false; } } if (!se.emitEnd()) { return false; } 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->isAsync() && !funbox->function()->explicitName(); } bool BytecodeEmitter::allocateResumeIndexForCurrentOffset(uint32_t* resumeIndex) { static constexpr uint32_t MaxResumeIndex = JS_BITMASK(24); static_assert(MaxResumeIndex < uint32_t(GeneratorObject::RESUME_INDEX_CLOSING), "resumeIndex should not include magic GeneratorObject resumeIndex values"); *resumeIndex = resumeOffsetList.length(); if (*resumeIndex > MaxResumeIndex) { reportError(nullptr, JSMSG_TOO_MANY_RESUME_INDEXES); return false; } return resumeOffsetList.append(offset()); } bool BytecodeEmitter::emitYieldOp(JSOp op) { if (op == JSOP_FINALYIELDRVAL) { return emit1(JSOP_FINALYIELDRVAL); } MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD || op == JSOP_AWAIT); ptrdiff_t off; if (!emitN(op, 3, &off)) { return false; } if (op == JSOP_AWAIT) { resumeOffsetList.numAwaits++; } else { resumeOffsetList.numYields++; } uint32_t resumeIndex; if (!allocateResumeIndexForCurrentOffset(&resumeIndex)) { return false; } SET_UINT24(code(off), resumeIndex); return emit1(JSOP_DEBUGAFTERYIELD); } bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) { // ParseNodeKind::SetThis is used to update |this| after a super() call // in a derived class constructor. MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis)); MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name)); RootedAtom name(cx, setThisNode->left()->as().name()); // The 'this' binding is not lexical, but due to super() semantics this // initialization needs to be treated as a lexical one. NameLocation loc = lookupName(name); NameLocation lexicalLoc; if (loc.kind() == NameLocation::Kind::FrameSlot) { lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot()); } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { EnvironmentCoordinate coord = loc.environmentCoordinate(); uint8_t hops = AssertedCast(coord.hops()); lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot()); } else { MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic); lexicalLoc = loc; } NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { // return false; } // Emit the new |this| value. if (!emitTree(setThisNode->right())) // NEWTHIS return false; // Get the original |this| and throw if we already initialized // it. Do *not* use the NameLocation argument, as that's the special // lexical location below to deal with super() semantics. if (!emitGetName(name)) { // NEWTHIS THIS return false; } if (!emit1(JSOP_CHECKTHISREINIT)) { // NEWTHIS THIS return false; } if (!emit1(JSOP_POP)) { // NEWTHIS return false; } if (!noe.emitAssignment()) { // NEWTHIS return false; } return true; } bool BytecodeEmitter::emitScript(ParseNode* body) { AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), body); setScriptStartOffsetIfUnset(body->pn_pos); TDZCheckCache tdzCache(this); EmitterScope emitterScope(this); if (sc->isGlobalContext()) { switchToPrologue(); if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) { return false; } switchToMain(); } else if (sc->isEvalContext()) { switchToPrologue(); if (!emitterScope.enterEval(this, sc->asEvalContext())) { return false; } switchToMain(); } else { MOZ_ASSERT(sc->isModuleContext()); if (!emitterScope.enterModule(this, sc->asModuleContext())) { return false; } } setFunctionBodyEndPos(body->pn_pos); if (sc->isEvalContext() && !sc->strict() && body->is() && !body->as().isEmptyScope()) { // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is // an immediately enclosed lexical scope, we need to enter the lexical // scope in the prologue for the DEFFUNs to pick up the right // environment chain. EmitterScope lexicalEmitterScope(this); LexicalScopeNode* scope = &body->as(); switchToPrologue(); if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, scope->scopeBindings())) { return false; } switchToMain(); if (!emitLexicalScopeBody(scope->scopeBody())) { return false; } if (!lexicalEmitterScope.leave(this)) { return false; } } else { if (!emitTree(body)) { return false; } } if (!updateSourceCoordNotes(body->pn_pos.end)) { return false; } if (!emit1(JSOP_RETRVAL)) { return false; } if (!emitterScope.leave(this)) { return false; } if (!NameFunctions(cx, body)) { return false; } if (!JSScript::fullyInitFromEmitter(cx, script, this)) { return false; } tellDebuggerAboutCompiledScript(cx); return true; } bool BytecodeEmitter::emitFunctionScript(CodeNode* funNode, TopLevelFunction isTopLevel) { MOZ_ASSERT(funNode->isKind(ParseNodeKind::Function)); ParseNode* body = funNode->body(); MOZ_ASSERT(body->isKind(ParseNodeKind::ParamsBody)); FunctionBox* funbox = sc->asFunctionBox(); AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), funbox); setScriptStartOffsetIfUnset(body->pn_pos); // The ordering of these EmitterScopes is important. The named lambda // scope needs to enclose the function scope needs to enclose the extra // var scope. Maybe namedLambdaEmitterScope; if (funbox->namedLambdaBindings()) { namedLambdaEmitterScope.emplace(this); if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) { 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. * * Also mark the script so that initializers created within it may be * given more precise types. */ if (isRunOnceLambda()) { script->setTreatAsRunOnce(); MOZ_ASSERT(!script->hasRunOnce()); switchToPrologue(); if (!emit1(JSOP_RUNONCE)) { return false; } switchToMain(); } setFunctionBodyEndPos(body->pn_pos); if (!emitTree(body)) { return false; } if (!updateSourceCoordNotes(body->pn_pos.end)) { 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 (namedLambdaEmitterScope) { if (!namedLambdaEmitterScope->leave(this)) { return false; } namedLambdaEmitterScope.reset(); } if (isTopLevel == TopLevelFunction::Yes) { if (!NameFunctions(cx, funNode)) { return false; } } if (!JSScript::fullyInitFromEmitter(cx, script, this)) { return false; } tellDebuggerAboutCompiledScript(cx); return true; } bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) { *emitted = 0; if (target->isKind(ParseNodeKind::Spread)) { target = target->as().kid(); } else if (target->isKind(ParseNodeKind::Assign)) { target = target->as().left(); } // No need to recur into ParseNodeKind::Array and // ParseNodeKind::Object subpatterns here, since // emitSetOrInitializeDestructuring does the recursion when // setting or initializing value. Getting reference doesn't recur. if (target->isKind(ParseNodeKind::Name) || target->isKind(ParseNodeKind::Array) || target->isKind(ParseNodeKind::Object)) { return true; } #ifdef DEBUG int depth = stackDepth; #endif switch (target->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &target->as(); bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE return false; } // SUPERBASE is pushed onto THIS in poe.prepareForRhs below. *emitted = 2; } else { if (!emitTree(&prop->expression())) { // OBJ return false; } *emitted = 1; } if (!poe.prepareForRhs()) { // [Super] // // THIS SUPERBASE // // [Other] // // OBJ return false; } break; } case ParseNodeKind::Elem: { PropertyByValue* elem = &target->as(); bool isSuper = elem->isSuper(); ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] // // THIS KEY // // [Other] // // OBJ KEY return false; } if (isSuper) { // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below. *emitted = 3; } else { *emitted = 2; } if (!eoe.prepareForRhs()) { // [Super] // // THIS KEY SUPERBASE // // [Other] // // OBJ KEY return false; } break; } case ParseNodeKind::Call: MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " "rejects function calls as assignment " "targets in destructuring assignments"); break; default: MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); } MOZ_ASSERT(stackDepth == depth + int(*emitted)); return true; } bool BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // 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(ParseNodeKind::Spread)) { target = target->as().kid(); } else if (target->isKind(ParseNodeKind::Assign)) { target = target->as().left(); } if (target->isKind(ParseNodeKind::Array) || target->isKind(ParseNodeKind::Object)) { if (!emitDestructuringOps(&target->as(), flav)) { return false; } // Per its post-condition, emitDestructuringOps has left the // to-be-destructured value on top of the stack. if (!emit1(JSOP_POP)) { return false; } } else { switch (target->getKind()) { case ParseNodeKind::Name: { RootedAtom name(cx, target->as().name()); NameLocation loc; NameOpEmitter::Kind kind; switch (flav) { case DestructuringDeclaration: loc = lookupName(name); kind = NameOpEmitter::Kind::Initialize; break; case DestructuringFormalParameterInVarScope: { // If there's an parameter expression var scope, the // destructuring declaration needs to initialize the name in // the function scope. The innermost scope is the var scope, // and its enclosing scope is the function scope. EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame(); loc = *locationOfNameBoundInScope(name, funScope); kind = NameOpEmitter::Kind::Initialize; break; } case DestructuringAssignment: loc = lookupName(name); kind = NameOpEmitter::Kind::SimpleAssignment; break; } NameOpEmitter noe(this, name, loc, kind); if (!noe.prepareForRhs()) { // V ENV? return false; } if (noe.emittedBindOp()) { // 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. // // In the cases where we are emitting a name op, emit a swap // because of this. if (!emit1(JSOP_SWAP)) { // ENV V return false; } } else { // In cases of emitting a frame slot or environment slot, // nothing needs be done. } if (!noe.emitAssignment()) { // V return false; } break; } case ParseNodeKind::Dot: { // The reference is already pushed by emitDestructuringLHSRef. // // [Super] // // THIS SUPERBASE VAL // // [Other] // // OBJ VAL PropertyAccess* prop = &target->as(); // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.skipObjAndRhs()) { return false; } if (!poe.emitAssignment(prop->key().atom())) { return false; // VAL } break; } case ParseNodeKind::Elem: { // The reference is already pushed by emitDestructuringLHSRef. // // [Super] // // THIS KEY SUPERBASE VAL // // [Other] // // OBJ KEY VAL PropertyByValue* elem = &target->as(); bool isSuper = elem->isSuper(); ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (!eoe.skipObjAndKeyAndRhs()) { return false; } if (!eoe.emitAssignment()) { // VAL return false; } break; } case ParseNodeKind::Call: MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " "rejects function calls as assignment " "targets in destructuring assignments"); break; default: MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. if (!emit1(JSOP_POP)) { // !STACK EMPTY! return false; } } return true; } bool BytecodeEmitter::emitIteratorNext(const Maybe& callSourceCoordOffset, IteratorKind iterKind /* = IteratorKind::Sync */, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); MOZ_ASSERT(this->stackDepth >= 2); // ... NEXT ITER if (!emitCall(JSOP_CALL, 0, callSourceCoordOffset)) { // ... RESULT return false; } if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // ... RESULT return false; } } if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { // ... RESULT return false; } checkTypeSet(JSOP_CALL); return true; } bool BytecodeEmitter::emitPushNotUndefinedOrNull() { MOZ_ASSERT(this->stackDepth > 0); // V if (!emit1(JSOP_DUP)) { // V V return false; } if (!emit1(JSOP_UNDEFINED)) { // V V UNDEFINED return false; } if (!emit1(JSOP_STRICTNE)) { // V ?NEQL return false; } JumpList undefinedOrNullJump; if (!emitJump(JSOP_AND, &undefinedOrNullJump)) { // V ?NEQL return false; } if (!emit1(JSOP_POP)) { // V return false; } if (!emit1(JSOP_DUP)) { // V V return false; } if (!emit1(JSOP_NULL)) { // V V NULL return false; } if (!emit1(JSOP_STRICTNE)) { // V ?NEQL return false; } if (!emitJumpTargetAndPatch(undefinedOrNullJump)) { // V NOT-UNDEF-OR-NULL return false; } return true; } bool BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope, IteratorKind iterKind /* = IteratorKind::Sync */, CompletionKind completionKind /* = CompletionKind::Normal */, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); // Generate inline logic corresponding to IteratorClose (ES 7.4.6). // // Callers need to ensure that the iterator object is at the top of the // stack. if (!emit1(JSOP_DUP)) { // ... ITER ITER return false; } // Step 3. // // Get the "return" method. if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) { // ... ITER RET return false; } // Step 4. // // Do nothing if "return" is undefined or null. InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) { // ... ITER RET NOT-UNDEF-OR-NULL return false; } if (!ifReturnMethodIsDefined.emitThenElse()) { // ... ITER RET return false; } if (completionKind == CompletionKind::Throw) { // 7.4.6 IteratorClose ( iterator, completion ) // ... // 3. Let return be ? GetMethod(iterator, "return"). // 4. If return is undefined, return Completion(completion). // 5. Let innerResult be Call(return, iterator, « »). // 6. If completion.[[Type]] is throw, return Completion(completion). // 7. If innerResult.[[Type]] is throw, return // Completion(innerResult). // // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET // is callable, and throws if not. Since step 6 doesn't match and // error handling in step 3 and step 7 can be merged. // // For CompletionKind::Throw case, an error thrown by JSOP_CALL for // step 5 is ignored by try-catch. So we should check if RET is // callable here, outside of try-catch, and the throw immediately if // not. CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; if (!emitCheckIsCallable(kind)) { // ... ITER RET return false; } } // Steps 5, 8. // // Call "return" if it is not undefined or null, and check that it returns // an Object. if (!emit1(JSOP_SWAP)) { // ... RET ITER return false; } Maybe tryCatch; if (completionKind == CompletionKind::Throw) { tryCatch.emplace(this, TryEmitter::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic); // Mutate stack to balance stack for try-catch. if (!emit1(JSOP_UNDEFINED)) { // ... RET ITER UNDEF return false; } if (!tryCatch->emitTry()) { // ... RET ITER UNDEF return false; } if (!emitDupAt(2)) { // ... RET ITER UNDEF RET return false; } if (!emitDupAt(2)) { // ... RET ITER UNDEF RET ITER return false; } } if (!emitCall(JSOP_CALL, 0)) { // ... ... RESULT return false; } checkTypeSet(JSOP_CALL); if (iterKind == IteratorKind::Async) { if (completionKind != CompletionKind::Throw) { // Await clobbers rval, so save the current rval. if (!emit1(JSOP_GETRVAL)) { // ... ... RESULT RVAL return false; } if (!emit1(JSOP_SWAP)) { // ... ... RVAL RESULT return false; } } if (!emitAwaitInScope(currentScope)) { // ... ... RVAL? RESULT return false; } } if (completionKind == CompletionKind::Throw) { if (!emit1(JSOP_SWAP)) { // ... RET ITER RESULT UNDEF return false; } if (!emit1(JSOP_POP)) { // ... RET ITER RESULT return false; } if (!tryCatch->emitCatch()) { // ... RET ITER RESULT return false; } // Just ignore the exception thrown by call and await. if (!emit1(JSOP_EXCEPTION)) { // ... RET ITER RESULT EXC return false; } if (!emit1(JSOP_POP)) { // ... RET ITER RESULT return false; } if (!tryCatch->emitEnd()) { // ... RET ITER RESULT return false; } // Restore stack. if (!emit2(JSOP_UNPICK, 2)) { // ... RESULT RET ITER return false; } if (!emitPopN(2)) { // ... RESULT return false; } } else { if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // ... RVAL? RESULT return false; } if (iterKind == IteratorKind::Async) { if (!emit1(JSOP_SWAP)) { // ... RESULT RVAL return false; } if (!emit1(JSOP_SETRVAL)) { // ... RESULT return false; } } } if (!ifReturnMethodIsDefined.emitElse()) { // ... ITER RET return false; } if (!emit1(JSOP_POP)) { // ... ITER return false; } if (!ifReturnMethodIsDefined.emitEnd()) { return false; } return emit1(JSOP_POP); // ... } template bool BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) { MOZ_ASSERT(this->stackDepth >= iterDepth); // Pad a nop at the beginning of the bytecode covered by the trynote so // that when unwinding environments, we may unwind to the scope // corresponding to the pc *before* the start, in case the first bytecode // emitted by |emitter| is the start of an inner scope. See comment above // UnwindEnvironmentToTryPc. if (!emit1(JSOP_TRY_DESTRUCTURING_ITERCLOSE)) { return false; } ptrdiff_t start = offset(); if (!emitter(this)) { return false; } ptrdiff_t end = offset(); if (start != end) { return addTryNote(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end); } return true; } bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { IfEmitter ifUndefined(this); if (!ifUndefined.emitIf(Nothing())) { return false; } 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; } if (!ifUndefined.emitThen()) { // VALUE return false; } if (!emit1(JSOP_POP)) { // return false; } if (!emitInitializer(defaultExpr, pattern)) { // DEFAULTVALUE return false; } if (!ifUndefined.emitEnd()) { // VALUE/DEFAULTVALUE return false; } return true; } bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name) { MOZ_ASSERT(maybeFun->isDirectRHSAnonFunction()); if (maybeFun->isKind(ParseNodeKind::Function)) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. JSFunction* fun = maybeFun->as().funbox()->function(); // The inferred name may already be set if this function is an // interpreted lazy function and we OOM'ed after we set the inferred // name the first time. if (fun->hasInferredName()) { MOZ_ASSERT(fun->isInterpretedLazy()); MOZ_ASSERT(fun->inferredName() == name); return true; } fun->setInferredName(name); return true; } MOZ_ASSERT(maybeFun->isKind(ParseNodeKind::Class)); uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) { return false; } if (!emitIndexOp(JSOP_STRING, nameIndex)) { // FUN NAME return false; } uint8_t kind = uint8_t(FunctionPrefixKind::None); if (!emit2(JSOP_SETFUNNAME, kind)) { // FUN return false; } return true; } bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { if (!emitTree(initializer)) { return false; } if (initializer->isDirectRHSAnonFunction()) { MOZ_ASSERT(!pattern->isInParens()); RootedAtom name(cx, pattern->as().name()); if (!setOrEmitSetFunName(initializer, name)) { return false; } } return true; } bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array)); MOZ_ASSERT(this->stackDepth != 0); // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // // Lines that are annotated "covered by trynote" mean that upon throwing // an exception, IteratorClose is called on iter only if done is false. // // let x, y; // let a, b, c, d; // let iter, next, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // next = iter.next; // // // ==== emitted by loop for a ==== // lref = GetReference(a); // covered by trynote // // result = Call(next, iter); // done = result.done; // // if (done) // value = undefined; // else // value = result.value; // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for b ==== // lref = GetReference(b); // covered by trynote // // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for elision ==== // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // // ==== emitted by loop for c ==== // lref = GetReference(c); // covered by trynote // // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // if (value === undefined) // value = y; // covered by trynote // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for d ==== // lref = GetReference(d); // covered by trynote // // if (done) // value = []; // else // value = [...iter]; // // SetOrInitialize(lref, value); // covered by trynote // // // === emitted after loop === // if (!done) // IteratorClose(iter); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. if (!emit1(JSOP_DUP)) { // ... OBJ OBJ return false; } if (!emitIterator()) { // ... OBJ NEXT ITER return false; } // For an empty pattern [], call IteratorClose unconditionally. Nothing // else needs to be done. if (!pattern->head()) { if (!emit1(JSOP_SWAP)) { // ... OBJ ITER NEXT return false; } if (!emit1(JSOP_POP)) { // ... OBJ ITER return false; } return emitIteratorCloseInInnermostScope(); // ... OBJ } // Push an initial FALSE value for DONE. if (!emit1(JSOP_FALSE)) { // ... OBJ NEXT ITER FALSE return false; } // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value // to be the second to top and the top of the stack, respectively. // IteratorClose is called upon exception only if done is false. int32_t tryNoteDepth = stackDepth; for (ParseNode* member : pattern->contents()) { bool isFirst = member == pattern->head(); DebugOnly hasNext = !!member->pn_next; size_t emitted = 0; // Spec requires LHS reference to be evaluated first. ParseNode* lhsPattern = member; if (lhsPattern->isKind(ParseNodeKind::Assign)) { lhsPattern = lhsPattern->as().left(); } bool isElision = lhsPattern->isKind(ParseNodeKind::Elision); if (!isElision) { auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ NEXT ITER DONE *LREF }; if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef)) { return false; } } // Pick the DONE value to the top of the stack. if (emitted) { if (!emit2(JSOP_PICK, emitted)) { // ... OBJ NEXT ITER *LREF DONE return false; } } if (isFirst) { // If this element is the first, DONE is always FALSE, so pop it. // // Non-first elements should emit if-else depending on the // member pattern, below. if (!emit1(JSOP_POP)) { // ... OBJ NEXT ITER *LREF return false; } } if (member->isKind(ParseNodeKind::Spread)) { InternalIfEmitter ifThenElse(this); if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. // ... OBJ NEXT ITER *LREF DONE if (!ifThenElse.emitThenElse()) { // ... OBJ NEXT ITER *LREF return false; } if (!emitUint32Operand(JSOP_NEWARRAY, 0)) { // ... OBJ NEXT ITER *LREF ARRAY return false; } if (!ifThenElse.emitElse()) { // ... OBJ NEXT ITER *LREF return false; } } // If iterator is not completed, create a new array with the rest // of the iterator. if (!emitDupAt(emitted + 1)) { // ... OBJ NEXT ITER *LREF NEXT return false; } if (!emitDupAt(emitted + 1)) { // ... OBJ NEXT ITER *LREF NEXT ITER return false; } if (!emitUint32Operand(JSOP_NEWARRAY, 0)) { // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY return false; } if (!emitNumberOp(0)) { // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY INDEX return false; } if (!emitSpread()) { // ... OBJ NEXT ITER *LREF ARRAY INDEX return false; } if (!emit1(JSOP_POP)) { // ... OBJ NEXT ITER *LREF ARRAY return false; } if (!isFirst) { if (!ifThenElse.emitEnd()) { return false; } MOZ_ASSERT(ifThenElse.pushed() == 1); } // At this point the iterator is done. Unpick a TRUE value for DONE above ITER. if (!emit1(JSOP_TRUE)) { // ... OBJ NEXT ITER *LREF ARRAY TRUE return false; } if (!emit2(JSOP_UNPICK, emitted + 1)) { // ... OBJ NEXT ITER TRUE *LREF ARRAY return false; } auto emitAssignment = [member, flav](BytecodeEmitter* bce) { return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ NEXT ITER TRUE }; if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) { return false; } MOZ_ASSERT(!hasNext); break; } ParseNode* pndefault = nullptr; if (member->isKind(ParseNodeKind::Assign)) { pndefault = member->as().right(); } MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread)); InternalIfEmitter ifAlreadyDone(this); if (!isFirst) { // ... OBJ NEXT ITER *LREF DONE if (!ifAlreadyDone.emitThenElse()) { // ... OBJ NEXT ITER *LREF return false; } if (!emit1(JSOP_UNDEFINED)) { // ... OBJ NEXT ITER *LREF UNDEF return false; } if (!emit1(JSOP_NOP_DESTRUCTURING)) { // ... OBJ NEXT ITER *LREF UNDEF return false; } // The iterator is done. Unpick a TRUE value for DONE above ITER. if (!emit1(JSOP_TRUE)) { // ... OBJ NEXT ITER *LREF UNDEF TRUE return false; } if (!emit2(JSOP_UNPICK, emitted + 1)) { // ... OBJ NEXT ITER TRUE *LREF UNDEF return false; } if (!ifAlreadyDone.emitElse()) { // ... OBJ NEXT ITER *LREF return false; } } if (!emitDupAt(emitted + 1)) { // ... OBJ NEXT ITER *LREF NEXT return false; } if (!emitDupAt(emitted + 1)) { // ... OBJ NEXT ITER *LREF NEXT ITER return false; } if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { // ... OBJ NEXT ITER *LREF RESULT return false; } if (!emit1(JSOP_DUP)) { // ... OBJ NEXT ITER *LREF RESULT RESULT return false; } if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) { // ... OBJ NEXT ITER *LREF RESULT DONE return false; } if (!emit1(JSOP_DUP)) { // ... OBJ NEXT ITER *LREF RESULT DONE DONE return false; } if (!emit2(JSOP_UNPICK, emitted + 2)) { // ... OBJ NEXT ITER DONE *LREF RESULT DONE return false; } InternalIfEmitter ifDone(this); if (!ifDone.emitThenElse()) { // ... OBJ NEXT ITER DONE *LREF RESULT return false; } if (!emit1(JSOP_POP)) { // ... OBJ NEXT ITER DONE *LREF return false; } if (!emit1(JSOP_UNDEFINED)) { // ... OBJ NEXT ITER DONE *LREF UNDEF return false; } if (!emit1(JSOP_NOP_DESTRUCTURING)) { // ... OBJ NEXT ITER DONE *LREF UNDEF return false; } if (!ifDone.emitElse()) { // ... OBJ NEXT ITER DONE *LREF RESULT return false; } if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // ... OBJ NEXT ITER DONE *LREF VALUE return false; } if (!ifDone.emitEnd()) { return false; } MOZ_ASSERT(ifDone.pushed() == 0); if (!isFirst) { if (!ifAlreadyDone.emitEnd()) { return false; } MOZ_ASSERT(ifAlreadyDone.pushed() == 2); } if (pndefault) { auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { return bce->emitDefault(pndefault, lhsPattern); // ... OBJ NEXT ITER DONE *LREF VALUE }; if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault)) { return false; } } if (!isElision) { auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ NEXT ITER DONE }; if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) { return false; } } else { if (!emit1(JSOP_POP)) { // ... OBJ NEXT ITER DONE return false; } } } // The last DONE value is on top of the stack. If not DONE, call // IteratorClose. // ... OBJ NEXT ITER DONE InternalIfEmitter ifDone(this); if (!ifDone.emitThenElse()) { // ... OBJ NEXT ITER return false; } if (!emitPopN(2)) { // ... OBJ return false; } if (!ifDone.emitElse()) { // ... OBJ NEXT ITER return false; } if (!emit1(JSOP_SWAP)) { // ... OBJ ITER NEXT return false; } if (!emit1(JSOP_POP)) { // ... OBJ ITER return false; } if (!emitIteratorCloseInInnermostScope()) { // ... OBJ return false; } if (!ifDone.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) { MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName)); return emitTree(computedPropName->kid()) && emit1(JSOP_TOID); } bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object)); MOZ_ASSERT(this->stackDepth > 0); // ... RHS if (!emit1(JSOP_CHECKOBJCOERCIBLE)) { // ... RHS return false; } bool needsRestPropertyExcludedSet = pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread); if (needsRestPropertyExcludedSet) { if (!emitDestructuringObjRestExclusionSet(pattern)) { // ... RHS SET return false; } if (!emit1(JSOP_SWAP)) { // ... SET RHS return false; } } for (ParseNode* member : pattern->contents()) { ParseNode* subpattern; if (member->isKind(ParseNodeKind::MutateProto) || member->isKind(ParseNodeKind::Spread)) { subpattern = member->as().kid(); } else { MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) || member->isKind(ParseNodeKind::Shorthand)); subpattern = member->as().right(); } ParseNode* lhs = subpattern; MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), !lhs->isKind(ParseNodeKind::Assign)); if (lhs->isKind(ParseNodeKind::Assign)) { lhs = lhs->as().left(); } size_t emitted; if (!emitDestructuringLHSRef(lhs, &emitted)) { // ... *SET RHS *LREF return false; } // Duplicate the value being destructured to use as a reference base. if (!emitDupAt(emitted)) { // ... *SET RHS *LREF RHS return false; } if (member->isKind(ParseNodeKind::Spread)) { if (!updateSourceCoordNotes(member->pn_pos.begin)) { return false; } if (!emitNewInit()) { // ... *SET RHS *LREF RHS TARGET return false; } if (!emit1(JSOP_DUP)) { // ... *SET RHS *LREF RHS TARGET TARGET return false; } if (!emit2(JSOP_PICK, 2)) { // ... *SET RHS *LREF TARGET TARGET RHS return false; } if (needsRestPropertyExcludedSet) { if (!emit2(JSOP_PICK, emitted + 4)) { // ... RHS *LREF TARGET TARGET RHS SET return false; } } CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered : CopyOption::Unfiltered; if (!emitCopyDataProperties(option)) { // ... RHS *LREF TARGET return false; } // Destructure TARGET per this member's lhs. if (!emitSetOrInitializeDestructuring(lhs, flav)) { // ... RHS return false; } MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); break; } // 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; if (member->isKind(ParseNodeKind::MutateProto)) { if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) { // ... *SET RHS *LREF PROP return false; } needsGetElem = false; } else { MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) || member->isKind(ParseNodeKind::Shorthand)); ParseNode* key = member->as().left(); if (key->isKind(ParseNodeKind::Number)) { if (!emitNumberOp(key->as().value())) { return false; // ... *SET RHS *LREF RHS KEY } } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String)) { if (!emitAtomOp(key->as().atom(), JSOP_GETPROP)) { return false; // ... *SET RHS *LREF PROP } needsGetElem = false; } else { if (!emitComputedPropertyName(&key->as())) { return false; // ... *SET RHS *LREF RHS KEY } // Add the computed property key to the exclusion set. if (needsRestPropertyExcludedSet) { if (!emitDupAt(emitted + 3)) { // ... SET RHS *LREF RHS KEY SET return false; } if (!emitDupAt(1)) { // ... SET RHS *LREF RHS KEY SET KEY return false; } if (!emit1(JSOP_UNDEFINED)) { // ... SET RHS *LREF RHS KEY SET KEY UNDEFINED return false; } if (!emit1(JSOP_INITELEM)) { // ... SET RHS *LREF RHS KEY SET return false; } if (!emit1(JSOP_POP)) { // ... SET RHS *LREF RHS KEY return false; } } } } // Get the property value if not done already. if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) { // ... *SET RHS *LREF PROP return false; } if (subpattern->isKind(ParseNodeKind::Assign)) { if (!emitDefault(subpattern->as().right(), lhs)) { return false; // ... *SET RHS *LREF VALUE } } // Destructure PROP per this member's lhs. if (!emitSetOrInitializeDestructuring(subpattern, flav)) { // ... *SET RHS return false; } } return true; } bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object)); MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread)); ptrdiff_t offset = this->offset(); if (!emitNewInit()) { 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. // In the case of computed property names and indices, we cannot fix the // shape at bytecode compile time. When the shape cannot be determined, // |obj| is nulled out. // No need to do any guessing for the object kind, since we know the upper // bound of how many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(pattern->count() - 1); RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); if (!obj) { return false; } RootedAtom pnatom(cx); for (ParseNode* member : pattern->contents()) { if (member->isKind(ParseNodeKind::Spread)) { break; } bool isIndex = false; if (member->isKind(ParseNodeKind::MutateProto)) { pnatom.set(cx->names().proto); } else { ParseNode* key = member->as().left(); if (key->isKind(ParseNodeKind::Number)) { if (!emitNumberOp(key->as().value())) { return false; } isIndex = true; } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String)) { pnatom.set(key->as().atom()); } else { // Otherwise this is a computed property name which needs to // be added dynamically. obj.set(nullptr); continue; } } // Initialize elements with |undefined|. if (!emit1(JSOP_UNDEFINED)) { return false; } if (isIndex) { obj.set(nullptr); if (!emit1(JSOP_INITELEM)) { return false; } } else { uint32_t index; if (!makeAtomIndex(pnatom, &index)) { return false; } if (obj) { MOZ_ASSERT(!obj->inDictionaryMode()); Rooted id(cx, AtomToId(pnatom)); if (!NativeDefineDataProperty(cx, obj, id, UndefinedHandleValue, JSPROP_ENUMERATE)) { return false; } if (obj->inDictionaryMode()) { obj.set(nullptr); } } if (!emitIndex32(JSOP_INITPROP, index)) { return false; } } } if (obj) { // The object survived and has a predictable shape: update the // original bytecode. if (!replaceNewInitWithNewObject(obj, offset)) { return false; } } return true; } bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern, DestructuringFlavor flav) { if (pattern->isKind(ParseNodeKind::Array)) { return emitDestructuringOpsArray(pattern, flav); } return emitDestructuringOpsObject(pattern, flav); } bool BytecodeEmitter::emitTemplateString(ListNode* templateString) { bool pushedString = false; for (ParseNode* item : templateString->contents()) { bool isString = (item->getKind() == ParseNodeKind::String || item->getKind() == ParseNodeKind::TemplateString); // 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 && item->as().atom()->empty()) { continue; } if (!isString) { // We update source notes before emitting the expression if (!updateSourceCoordNotes(item->pn_pos.begin)) { return false; } } if (!emitTree(item)) { 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::emitDeclarationList(ListNode* declList) { MOZ_ASSERT(declList->isOp(JSOP_NOP)); for (ParseNode* decl : declList->contents()) { if (!updateSourceCoordNotes(decl->pn_pos.begin)) { return false; } if (decl->isKind(ParseNodeKind::Assign)) { MOZ_ASSERT(decl->isOp(JSOP_NOP)); AssignmentNode* assignNode = &decl->as(); ListNode* pattern = &assignNode->left()->as(); MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array) || pattern->isKind(ParseNodeKind::Object)); if (!emitTree(assignNode->right())) { return false; } if (!emitDestructuringOps(pattern, DestructuringDeclaration)) { return false; } if (!emit1(JSOP_POP)) { return false; } } else { NameNode* name = &decl->as(); if (!emitSingleDeclaration(declList, name, name->initializer())) { return false; } } } return true; } bool BytecodeEmitter::emitSingleDeclaration(ListNode* declList, NameNode* decl, ParseNode* initializer) { MOZ_ASSERT(decl->isKind(ParseNodeKind::Name)); // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. if (!initializer && declList->isKind(ParseNodeKind::Var)) { return true; } NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { // ENV? return false; } if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. MOZ_ASSERT(declList->isKind(ParseNodeKind::Let), "var declarations without initializers handled above, " "and const declarations must have initializers"); if (!emit1(JSOP_UNDEFINED)) { // ENV? UNDEF return false; } } else { MOZ_ASSERT(initializer); if (!emitInitializer(initializer, decl)) { // ENV? V return false; } } if (!noe.emitAssignment()) { // V return false; } if (!emit1(JSOP_POP)) { // return false; } return true; } static bool EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) { // If there is a RHS tree, emit the tree. if (rhs) { return bce->emitTree(rhs); } // Otherwise the RHS value to assign is already on the stack, i.e., the // next enumeration value in a for-in or for-of loop. Depending on how // many other values have been pushed on the stack, we need to get the // already-pushed RHS value. if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1)) { return false; } return true; } static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) { switch (pnk) { case ParseNodeKind::Assign: return JSOP_NOP; case ParseNodeKind::AddAssign: return JSOP_ADD; case ParseNodeKind::SubAssign: return JSOP_SUB; case ParseNodeKind::BitOrAssign: return JSOP_BITOR; case ParseNodeKind::BitXorAssign: return JSOP_BITXOR; case ParseNodeKind::BitAndAssign: return JSOP_BITAND; case ParseNodeKind::LshAssign: return JSOP_LSH; case ParseNodeKind::RshAssign: return JSOP_RSH; case ParseNodeKind::UrshAssign: return JSOP_URSH; case ParseNodeKind::MulAssign: return JSOP_MUL; case ParseNodeKind::DivAssign: return JSOP_DIV; case ParseNodeKind::ModAssign: return JSOP_MOD; case ParseNodeKind::PowAssign: return JSOP_POW; default: MOZ_CRASH("unexpected compound assignment op"); } } bool BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) { bool isCompound = compoundOp != JSOP_NOP; // Name assignments are handled separately because choosing ops and when // to emit BINDNAME is involved and should avoid duplication. if (lhs->isKind(ParseNodeKind::Name)) { NameNode* nameNode = &lhs->as(); RootedAtom name(cx, nameNode->name()); NameOpEmitter noe(this, name, isCompound ? NameOpEmitter::Kind::CompoundAssignment : NameOpEmitter::Kind::SimpleAssignment); if (!noe.prepareForRhs()) { // ENV? VAL? return false; } // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on // the top of the stack and we need to pick the right RHS value. uint8_t offset = noe.emittedBindOp() ? 2 : 1; if (!EmitAssignmentRhs(this, rhs, offset)) { // ENV? VAL? RHS return false; } if (rhs && rhs->isDirectRHSAnonFunction()) { MOZ_ASSERT(!nameNode->isInParens()); MOZ_ASSERT(!isCompound); if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS return false; } } // Emit the compound assignment op if there is one. if (isCompound) { if (!emit1(compoundOp)) { // ENV? VAL return false; } } if (!noe.emitAssignment()) { // VAL return false; } return true; } Maybe poe; Maybe eoe; // Deal with non-name assignments. uint8_t offset = 1; switch (lhs->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &lhs->as(); bool isSuper = prop->isSuper(); poe.emplace(this, isCompound ? PropOpEmitter::Kind::CompoundAssignment : PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe->prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE return false; } // SUPERBASE is pushed onto THIS later in poe->emitGet below. offset += 2; } else { if (!emitTree(&prop->expression())) { // OBJ return false; } offset += 1; } break; } case ParseNodeKind::Elem: { PropertyByValue* elem = &lhs->as(); bool isSuper = elem->isSuper(); eoe.emplace(this, isCompound ? ElemOpEmitter::Kind::CompoundAssignment : ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (!emitElemObjAndKey(elem, isSuper, *eoe)) { // [Super] // // THIS KEY // // [Other] // // OBJ KEY return false; } if (isSuper) { // SUPERBASE is pushed onto KEY in eoe->emitGet below. offset += 3; } else { offset += 2; } break; } case ParseNodeKind::Array: case ParseNodeKind::Object: break; case ParseNodeKind::Call: if (!emitTree(lhs)) { return false; } // Assignment to function calls is forbidden, but we have to make the // call first. Now we can throw. if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) { return false; } // Rebalance the stack to placate stack-depth assertions. if (!emit1(JSOP_POP)) { return false; } break; default: MOZ_ASSERT(0); } if (isCompound) { MOZ_ASSERT(rhs); switch (lhs->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &lhs->as(); // TODO(khyperia): Implement private field access. if (!poe->emitGet(prop->key().atom())) { // [Super] // // THIS SUPERBASE PROP // // [Other] // // OBJ PROP return false; } break; } case ParseNodeKind::Elem: { if (!eoe->emitGet()) { // KEY THIS OBJ ELEM return false; } break; } case ParseNodeKind::Call: // We just emitted a JSOP_THROWMSG and popped the call's return // value. Push a random value to make sure the stack depth is // correct. if (!emit1(JSOP_NULL)) { return false; } break; default:; } } switch (lhs->getKind()) { case ParseNodeKind::Dot: if (!poe->prepareForRhs()) { // [Simple,Super] // // THIS SUPERBASE // // [Simple,Other] // // OBJ // // [Compound,Super] // // THIS SUPERBASE PROP // // [Compound,Other] // // OBJ PROP return false; } break; case ParseNodeKind::Elem: if (!eoe->prepareForRhs()) { // [Simple,Super] // // THIS KEY SUPERBASE // // [Simple,Other] // // OBJ KEY // // [Compound,Super] // // THIS KEY SUPERBASE ELEM // // [Compound,Other] // // OBJ KEY ELEM return false; } break; default: break; } if (!EmitAssignmentRhs(this, rhs, offset)) { // ... VAL? RHS return false; } /* If += etc., emit the binary operator with a source note. */ if (isCompound) { if (!newSrcNote(SRC_ASSIGNOP)) { return false; } if (!emit1(compoundOp)) { // ... VAL return false; } } /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { case ParseNodeKind::Dot: { PropertyAccess* prop = &lhs->as(); // TODO(khyperia): Implement private field access. if (!poe->emitAssignment(prop->key().atom())) { // VAL return false; } poe.reset(); break; } case ParseNodeKind::Call: // We threw above, so nothing to do here. break; case ParseNodeKind::Elem: { if (!eoe->emitAssignment()) { // VAL return false; } eoe.reset(); break; } case ParseNodeKind::Array: case ParseNodeKind::Object: if (!emitDestructuringOps(&lhs->as(), DestructuringAssignment)) { return false; } break; default: MOZ_ASSERT(0); } return true; } bool ParseNode::getConstantValue(JSContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, Value* compare, size_t ncompare, NewObjectKind newKind) { MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject); switch (getKind()) { case ParseNodeKind::Number: vp.setNumber(as().value()); return true; case ParseNodeKind::TemplateString: case ParseNodeKind::String: vp.setString(as().atom()); return true; case ParseNodeKind::True: vp.setBoolean(true); return true; case ParseNodeKind::False: vp.setBoolean(false); return true; case ParseNodeKind::Null: vp.setNull(); return true; case ParseNodeKind::RawUndefined: vp.setUndefined(); return true; case ParseNodeKind::CallSiteObj: case ParseNodeKind::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() == ParseNodeKind::CallSiteObj) { count = as().count() - 1; pn = as().head()->pn_next; } else { MOZ_ASSERT(!as().hasNonConstInitializer()); count = as().count(); pn = as().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); ArrayObject* 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 ParseNodeKind::Object: { MOZ_ASSERT(!as().hasNonConstInitializer()); if (allowObjects == DontAllowObjects) { vp.setMagic(JS_GENERIC_MAGIC); return true; } MOZ_ASSERT(allowObjects == AllowObjects); Rooted properties(cx, IdValueVector(cx)); RootedValue value(cx), idvalue(cx); for (ParseNode* item : as().contents()) { // MutateProto and Spread, both are unary, cannot appear here. BinaryNode* prop = &item->as(); if (!prop->right()->getConstantValue(cx, allowObjects, &value)) { return false; } if (value.isMagic(JS_GENERIC_MAGIC)) { vp.setMagic(JS_GENERIC_MAGIC); return true; } ParseNode* key = prop->left(); if (key->isKind(ParseNodeKind::Number)) { idvalue = NumberValue(key->as().value()); } else { MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String)); MOZ_ASSERT(key->as().atom() != cx->names().proto); idvalue = StringValue(key->as().atom()); } RootedId id(cx); if (!ValueToId(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(ListNode* objOrArray) { NewObjectKind newKind = (objOrArray->getKind() == ParseNodeKind::Object) ? SingletonObject : TenuredObject; RootedValue value(cx); if (!objOrArray->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(CallSiteNode* callSiteObj) { RootedValue value(cx); if (!callSiteObj->getConstantValue(cx, ParseNode::AllowObjects, &value)) { return false; } MOZ_ASSERT(value.isObject()); ObjectBox* objbox1 = parser->newObjectBox(&value.toObject()); if (!objbox1) { return false; } if (!callSiteObj->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); } namespace { class EmitLevelManager { BytecodeEmitter* bce; public: explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; } ~EmitLevelManager() { bce->emitLevel--; } }; } /* anonymous namespace */ bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { // We must be nested under a try-finally statement. MOZ_ASSERT(innermostNestableControl->is()); /* Pick up the pending exception and bind it to the catch variable. */ if (!emit1(JSOP_EXCEPTION)) { return false; } ParseNode* param = catchClause->left(); if (!param) { // Catch parameter was omitted; just discard the exception. if (!emit1(JSOP_POP)) { return false; } } else { switch (param->getKind()) { case ParseNodeKind::Array: case ParseNodeKind::Object: if (!emitDestructuringOps(¶m->as(), DestructuringDeclaration)) { return false; } if (!emit1(JSOP_POP)) { return false; } break; case ParseNodeKind::Name: if (!emitLexicalInitialization(¶m->as())) { return false; } if (!emit1(JSOP_POP)) { return false; } break; default: MOZ_ASSERT(0); } } /* Emit the catch body. */ return emitTree(catchClause->right()); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the // comment on EmitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(TryNode* tryNode) { LexicalScopeNode* catchScope = tryNode->catchScope(); ParseNode* finallyNode = tryNode->finallyBlock(); TryEmitter::Kind kind; if (catchScope) { if (finallyNode) { kind = TryEmitter::Kind::TryCatchFinally; } else { kind = TryEmitter::Kind::TryCatch; } } else { MOZ_ASSERT(finallyNode); kind = TryEmitter::Kind::TryFinally; } TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic); if (!tryCatch.emitTry()) { return false; } if (!emitTree(tryNode->body())) { return false; } // If this try has a catch block, emit it. if (catchScope) { // The emitted code for a catch block looks like: // // [pushlexicalenv] only if any local aliased // exception // setlocal 0; pop assign or possibly destructure exception // < catch block contents > // debugleaveblock // [poplexicalenv] only if any local aliased // if there is a finally block: // gosub // goto if (!tryCatch.emitCatch()) { return false; } // Emit the lexical scope and catch body. if (!emitTree(catchScope)) { return false; } } // Emit the finally handler, if there is one. if (finallyNode) { if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) { return false; } if (!emitTree(finallyNode)) { return false; } } if (!tryCatch.emitEnd()) { return false; } return true; } +MOZ_MUST_USE bool +BytecodeEmitter::emitGoSub(JumpList* jump) +{ + if (!emit1(JSOP_FALSE)) { + return false; + } + + ptrdiff_t off; + if (!emitN(JSOP_RESUMEINDEX, 3, &off)) { + return false; + } + + if (!emitJump(JSOP_GOSUB, jump)) { + return false; + } + + uint32_t resumeIndex; + if (!allocateResumeIndexForCurrentOffset(&resumeIndex)) { + return false; + } + + SET_UINT24(code(off), resumeIndex); + return true; +} + bool BytecodeEmitter::emitIf(TernaryNode* ifNode) { IfEmitter ifThenElse(this); if (!ifThenElse.emitIf(Some(ifNode->pn_pos.begin))) { return false; } if_again: /* Emit code for the condition before pushing stmtInfo. */ if (!emitTree(ifNode->kid1())) { return false; } ParseNode* elseNode = ifNode->kid3(); if (elseNode) { if (!ifThenElse.emitThenElse()) { return false; } } else { if (!ifThenElse.emitThen()) { return false; } } /* Emit code for the then part. */ if (!emitTree(ifNode->kid2())) { return false; } if (elseNode) { if (elseNode->isKind(ParseNodeKind::If)) { ifNode = &elseNode->as(); if (!ifThenElse.emitElseIf(Some(ifNode->pn_pos.begin))) { return false; } goto if_again; } if (!ifThenElse.emitElse()) { return false; } /* Emit code for the else part. */ if (!emitTree(elseNode)) { return false; } } if (!ifThenElse.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList) { MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations()); for (ParseNode* stmt : stmtList->contents()) { ParseNode* maybeFun = stmt; if (!sc->strict()) { while (maybeFun->isKind(ParseNodeKind::Label)) { maybeFun = maybeFun->as().statement(); } } if (maybeFun->isKind(ParseNodeKind::Function) && maybeFun->as().functionIsHoisted()) { if (!emitTree(maybeFun)) { return false; } } } return true; } bool BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote) { if (body->isKind(ParseNodeKind::StatementList) && body->as().hasTopLevelFunctionDeclarations()) { // This block contains function statements whose definitions are // hoisted to the top of the block. Emit these as a separate pass // before the rest of the block. if (!emitHoistedFunctionsInList(&body->as())) { return false; } } // Line notes were updated by emitLexicalScope. return emitTree(body, ValueUsage::WantValue, emitLineNote); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) { TDZCheckCache tdzCache(this); ParseNode* body = lexicalScope->scopeBody(); if (lexicalScope->isEmptyScope()) { return emitLexicalScopeBody(body); } // We are about to emit some bytecode for what the spec calls "declaration // instantiation". Assign these instructions to the opening `{` of the // block. (Using the location of each declaration we're instantiating is // too weird when stepping in the debugger.) if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { if (!updateSourceCoordNotes(lexicalScope->pn_pos.begin)) { return false; } } EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(ParseNodeKind::Catch)) { BinaryNode* catchNode = &body->as(); kind = (!catchNode->left() || catchNode->left()->isKind(ParseNodeKind::Name)) ? ScopeKind::SimpleCatch : ScopeKind::Catch; } else { kind = ScopeKind::Lexical; } if (!emitterScope.enterLexical(this, kind, lexicalScope->scopeBindings())) { return false; } if (body->isKind(ParseNodeKind::For)) { // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are // lexical declarations in the head. Signal this by passing a // non-nullptr lexical scope. if (!emitFor(&body->as(), &emitterScope)) { return false; } } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) { return false; } } return emitterScope.leave(this); } bool BytecodeEmitter::emitWith(BinaryNode* withNode) { // Ensure that the column of the 'with' is set properly. if (!updateSourceCoordNotes(withNode->pn_pos.begin)) { return false; } if (!emitTree(withNode->left())) { return false; } EmitterScope emitterScope(this); if (!emitterScope.enterWith(this)) { return false; } if (!emitTree(withNode->right())) { return false; } return emitterScope.leave(this); } bool BytecodeEmitter::emitCopyDataProperties(CopyOption option) { DebugOnly depth = this->stackDepth; uint32_t argc; if (option == CopyOption::Filtered) { MOZ_ASSERT(depth > 2); // TARGET SOURCE SET argc = 3; if (!emitAtomOp(cx->names().CopyDataProperties, JSOP_GETINTRINSIC)) // TARGET SOURCE SET COPYDATAPROPERTIES { return false; } } else { MOZ_ASSERT(depth > 1); // TARGET SOURCE argc = 2; if (!emitAtomOp(cx->names().CopyDataPropertiesUnfiltered, JSOP_GETINTRINSIC)) // TARGET SOURCE COPYDATAPROPERTIES { return false; } } if (!emit1(JSOP_UNDEFINED)) { // TARGET SOURCE *SET COPYDATAPROPERTIES UNDEFINED return false; } if (!emit2(JSOP_PICK, argc + 1)) { // SOURCE *SET COPYDATAPROPERTIES UNDEFINED TARGET return false; } if (!emit2(JSOP_PICK, argc + 1)) { // *SET COPYDATAPROPERTIES UNDEFINED TARGET SOURCE return false; } if (option == CopyOption::Filtered) { if (!emit2(JSOP_PICK, argc + 1)) { // COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET return false; } } if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) { // IGNORED return false; } checkTypeSet(JSOP_CALL_IGNORES_RV); if (!emit1(JSOP_POP)) { // - return false; } MOZ_ASSERT(depth - int(argc) == 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); if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // ITER return false; } if (!emit1(JSOP_DUP)) { // ITER ITER return false; } if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) { // ITER NEXT return false; } if (!emit1(JSOP_SWAP)) { // NEXT ITER return false; } return true; } bool BytecodeEmitter::emitAsyncIterator() { // Convert iterable to iterator. if (!emit1(JSOP_DUP)) { // OBJ OBJ return false; } if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::asyncIterator))) { // OBJ OBJ @@ASYNCITERATOR return false; } if (!emitElemOpBase(JSOP_CALLELEM)) { // OBJ ITERFN return false; } InternalIfEmitter ifAsyncIterIsUndefined(this); if (!emitPushNotUndefinedOrNull()) { // OBJ ITERFN !UNDEF-OR-NULL return false; } if (!emit1(JSOP_NOT)) { // OBJ ITERFN UNDEF-OR-NULL return false; } if (!ifAsyncIterIsUndefined.emitThenElse()) { // OBJ ITERFN return false; } if (!emit1(JSOP_POP)) { // OBJ return false; } 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); if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // ITER return false; } if (!emit1(JSOP_DUP)) { // ITER ITER return false; } if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) { // ITER SYNCNEXT return false; } if (!emit1(JSOP_TOASYNCITER)) { // ITER return false; } if (!ifAsyncIterIsUndefined.emitElse()) { // OBJ ITERFN return false; } if (!emit1(JSOP_SWAP)) { // ITERFN OBJ return false; } if (!emitCall(JSOP_CALLITER, 0)) { // ITER return false; } checkTypeSet(JSOP_CALLITER); if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // ITER return false; } if (!ifAsyncIterIsUndefined.emitEnd()) { // ITER return false; } if (!emit1(JSOP_DUP)) { // ITER ITER return false; } if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) { // ITER NEXT return false; } if (!emit1(JSOP_SWAP)) { // NEXT ITER return false; } return true; } bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { LoopControl loopInfo(this, StatementKind::Spread); // 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; } // Jump down to the loop condition to minimize overhead, assuming at least // one iteration. (This is also what we do for loops; whether this // assumption holds for spreads is an unanswered question.) if (!loopInfo.emitEntryJump(this)) { // NEXT ITER ARR I (during the goto) return false; } if (!loopInfo.emitLoopHead(this, Nothing())) { // NEXT ITER ARR I return false; } // When we enter the goto above, we have NEXT ITER ARR I on the stack. But // when we reach this point on the loop backedge (if spreading produces at // least one value), we've additionally pushed a RESULT iteration value. // Increment manually to reflect this. this->stackDepth++; { #ifdef DEBUG auto loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // NEXT ITER ARR I VALUE return false; } if (!emit1(JSOP_INITELEM_INC)) { // NEXT ITER ARR (I+1) return false; } MOZ_ASSERT(this->stackDepth == loopDepth - 1); // Spread operations can't contain |continue|, so don't bother setting loop // and enclosing "update" offsets, as we do with for-loops. // COME FROM the beginning of the loop to here. if (!loopInfo.emitLoopEntry(this, Nothing())) { // NEXT ITER ARR I return false; } if (!emitDupAt(3)) { // NEXT ITER ARR I NEXT return false; } if (!emitDupAt(3)) { // NEXT ITER ARR I NEXT ITER return false; } if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) { return false; // ITER ARR I RESULT } if (!emit1(JSOP_DUP)) { // NEXT ITER ARR I RESULT RESULT return false; } if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) { // NEXT ITER ARR I RESULT DONE return false; } if (!loopInfo.emitLoopEnd(this, JSOP_IFEQ)) { // NEXT ITER ARR I RESULT return false; } MOZ_ASSERT(this->stackDepth == loopDepth); } // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, SrcNote::ForOf::BackJumpOffset, loopInfo.loopEndOffsetFromEntryJump())) { return false; } // No breaks or continues should occur in spreads. MOZ_ASSERT(loopInfo.breaks.offset == -1); MOZ_ASSERT(loopInfo.continues.offset == -1); if (!addTryNote(JSTRY_FOR_OF, stackDepth, loopInfo.headOffset(), loopInfo.breakTargetOffset())) { return false; } if (!emit2(JSOP_PICK, 4)) { // ITER ARR FINAL_INDEX RESULT NEXT return false; } if (!emit2(JSOP_PICK, 4)) { // ARR FINAL_INDEX RESULT NEXT ITER return false; } return emitPopN(3); // ARR FINAL_INDEX } bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) { MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || forHead->isKind(ParseNodeKind::ForOf)); MOZ_ASSERT(this->stackDepth >= 1, "must have a per-iteration value for initializing"); ParseNode* target = forHead->kid1(); MOZ_ASSERT(!forHead->kid2()); // If the for-in/of loop didn't have a variable declaration, per-loop // initialization is just assigning the iteration value to a target // expression. if (!parser->astGenerator().isDeclarationList(target)) { return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL } // Otherwise, per-loop initialization is (possibly) declaration // initialization. If the declaration is a lexical declaration, it must be // initialized. If the declaration is a variable declaration, an // assignment to that name (which does *not* necessarily assign to the // variable!) must be generated. if (!updateSourceCoordNotes(target->pn_pos.begin)) { return false; } MOZ_ASSERT(target->isForLoopDeclaration()); target = parser->astGenerator().singleBindingFromDeclaration(&target->as()); if (target->isKind(ParseNodeKind::Name)) { NameNode* nameNode = &target->as(); NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (noe.emittedBindOp()) { // Per-iteration initialization in for-in/of loops computes the // iteration value *before* initializing. Thus the initializing // value may be buried under a bind-specific value on the stack. // Swap it to the top of the stack. MOZ_ASSERT(stackDepth >= 2); if (!emit1(JSOP_SWAP)) { return false; } } else { // In cases of emitting a frame slot or environment slot, // nothing needs be done. MOZ_ASSERT(stackDepth >= 1); } if (!noe.emitAssignment()) { return false; } // The caller handles removing the iteration value from the stack. return true; } MOZ_ASSERT(!target->isKind(ParseNodeKind::Assign), "for-in/of loop destructuring declarations can't have initializers"); MOZ_ASSERT(target->isKind(ParseNodeKind::Array) || target->isKind(ParseNodeKind::Object)); return emitDestructuringOps(&target->as(), DestructuringDeclaration); } bool BytecodeEmitter::emitForOf(ForNode* forOfLoop, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::For)); TernaryNode* forOfHead = forOfLoop->head(); MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf)); unsigned iflags = forOfLoop->iflags(); IteratorKind iterKind = (iflags & JSITER_FORAWAITOF) ? IteratorKind::Async : IteratorKind::Sync; MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asFunctionBox()); MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asFunctionBox()->isAsync()); ParseNode* forHeadExpr = forOfHead->kid3(); // Certain builtins (e.g. Array.from) are implemented in self-hosting // as for-of loops. bool allowSelfHostedIter = false; if (emitterMode == BytecodeEmitter::SelfHosting && forHeadExpr->isKind(ParseNodeKind::Call) && forHeadExpr->as().left()->isName(cx->names().allowContentIter)) { allowSelfHostedIter = true; } ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter, iterKind); if (!forOf.emitIterated()) { // return false; } if (!emitTree(forHeadExpr)) { // ITERABLE return false; } if (headLexicalEmitterScope) { DebugOnly forOfTarget = forOfHead->kid1(); MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::Let) || forOfTarget->isKind(ParseNodeKind::Const)); } if (!forOf.emitInitialize(Some(forOfHead->pn_pos.begin))) { return false; // NEXT ITER VALUE } if (!emitInitializeForInOrOfTarget(forOfHead)) { // NEXT ITER VALUE return false; } if (!forOf.emitBody()) { // NEXT ITER UNDEF return false; } // Perform the loop body. ParseNode* forBody = forOfLoop->body(); if (!emitTree(forBody)) { // NEXT ITER UNDEF return false; } if (!forOf.emitEnd(Some(forHeadExpr->pn_pos.begin))) { // return false; } return true; } bool BytecodeEmitter::emitForIn(ForNode* forInLoop, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forInLoop->isOp(JSOP_ITER)); TernaryNode* forInHead = forInLoop->head(); MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn)); ForInEmitter forIn(this, headLexicalEmitterScope); // Annex B: Evaluate the var-initializer expression if present. // |for (var i = initializer in expr) { ... }| ParseNode* forInTarget = forInHead->kid1(); if (parser->astGenerator().isDeclarationList(forInTarget)) { ParseNode* decl = parser->astGenerator().singleBindingFromDeclaration(&forInTarget->as()); if (decl->isKind(ParseNodeKind::Name)) { if (ParseNode* initializer = decl->as().initializer()) { MOZ_ASSERT(forInTarget->isKind(ParseNodeKind::Var), "for-in initializers are only permitted for |var| declarations"); if (!updateSourceCoordNotes(decl->pn_pos.begin)) { return false; } NameNode* nameNode = &decl->as(); NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (!emitInitializer(initializer, decl)) { return false; } if (!noe.emitAssignment()) { return false; } // Pop the initializer. if (!emit1(JSOP_POP)) { return false; } } } } if (!forIn.emitIterated()) { // return false; } // Evaluate the expression being iterated. ParseNode* expr = forInHead->kid3(); if (!emitTree(expr)) { // EXPR return false; } MOZ_ASSERT(forInLoop->iflags() == 0); MOZ_ASSERT_IF(headLexicalEmitterScope, forInTarget->isKind(ParseNodeKind::Let) || forInTarget->isKind(ParseNodeKind::Const)); if (!forIn.emitInitialize()) { // ITER ITERVAL return false; } if (!emitInitializeForInOrOfTarget(forInHead)) { // ITER ITERVAL return false; } if (!forIn.emitBody()) { // ITER ITERVAL return false; } // Perform the loop body. ParseNode* forBody = forInLoop->body(); if (!emitTree(forBody)) { // ITER ITERVAL return false; } if (!forIn.emitEnd(Some(forInHead->pn_pos.begin))) { // return false; } return true; } /* C-style `for (init; cond; update) ...` loop. */ bool BytecodeEmitter::emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { TernaryNode* forHead = forNode->head(); ParseNode* forBody = forNode->body(); ParseNode* init = forHead->kid1(); ParseNode* cond = forHead->kid2(); ParseNode* update = forHead->kid3(); bool isLet = init && init->isKind(ParseNodeKind::Let); CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr); if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) { return false; // } // If the head of this for-loop declared any lexical variables, the parser // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope // representing the implicit scope of those variables. By the time we get // here, we have already entered that scope. So far, so good. if (init) { // 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.) if (init->isForLoopDeclaration()) { if (!emitTree(init)) { // return false; } } else { // 'init' is an expression, not a declaration. emitTree left its // value on the stack. if (!emitTree(init, ValueUsage::IgnoreValue)) { // VAL return false; } if (!emit1(JSOP_POP)) { // return false; } } } if (!cfor.emitBody(cond ? CForEmitter::Cond::Present : CForEmitter::Cond::Missing, getOffsetForLoop(forBody))) // { return false; } if (!emitTree(forBody)) { // return false; } if (!cfor.emitUpdate(update ? CForEmitter::Update::Present : CForEmitter::Update::Missing, update ? Some(update->pn_pos.begin) : Nothing())) { // return false; } // Check for update code to do before the condition (if any). if (update) { if (!emitTree(update, ValueUsage::IgnoreValue)) { // VAL return false; } } if (!cfor.emitCond(Some(forNode->pn_pos.begin), cond ? Some(cond->pn_pos.begin) : Nothing(), Some(forNode->pn_pos.end))) // { return false; } if (cond) { if (!emitTree(cond)) { // VAL return false; } } if (!cfor.emitEnd()) { // return false; } return true; } bool BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { if (forNode->head()->isKind(ParseNodeKind::ForHead)) { return emitCStyleFor(forNode, headLexicalEmitterScope); } if (!updateLineNumberNotes(forNode->pn_pos.begin)) { return false; } if (forNode->head()->isKind(ParseNodeKind::ForIn)) { return emitForIn(forNode, headLexicalEmitterScope); } MOZ_ASSERT(forNode->head()->isKind(ParseNodeKind::ForOf)); return emitForOf(forNode, headLexicalEmitterScope); } MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(CodeNode* funNode, bool needsProto) { FunctionBox* funbox = funNode->funbox(); RootedFunction fun(cx, funbox->function()); RootedAtom name(cx, fun->explicitName()); 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) { // Annex B block-scoped functions are hoisted like any other // block-scoped function to the top of their scope. When their // definitions are seen for the second time, we need to emit the // assignment that assigns the function to the outer 'var' binding. if (funbox->isAnnexB) { // Get the location of the 'var' binding in the body scope. The // name must be found, else there is a bug in the Annex B handling // in Parser. // // In sloppy eval contexts, this location is dynamic. Maybe lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); // If there are parameter expressions, the var name could be a // parameter. if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) { lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); } if (!lhsLoc) { lhsLoc = Some(NameLocation::DynamicAnnexBVar()); } else { MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || lhsLoc->bindingKind() == BindingKind::FormalParameter || (lhsLoc->bindingKind() == BindingKind::Let && sc->asFunctionBox()->hasParameterExprs)); } NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); if (!noe.prepareForRhs()) { return false; } if (!emitGetName(name)) { return false; } if (!noe.emitAssignment()) { return false; } if (!emit1(JSOP_POP)) { return false; } } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); MOZ_ASSERT(funNode->functionIsHoisted()); 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()) { funbox->setEnclosingScopeForInnerLazyFunction(innermostScope()); if (emittingRunOnceLambda) { fun->lazyScript()->setTreatAsRunOnce(); } } else { MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); // Inherit most things (principals, version, etc) from the // parent. Use default values for the rest. Rooted parent(cx, script); MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); const JS::TransitiveCompileOptions& transitiveOptions = parser->options(); JS::CompileOptions options(cx, transitiveOptions); Rooted sourceObject(cx, script->sourceObject()); Rooted script(cx, JSScript::Create(cx, options, sourceObject, funbox->bufStart, funbox->bufEnd, funbox->toStringStart, funbox->toStringEnd)); if (!script) { return false; } EmitterMode nestedMode = emitterMode; if (nestedMode == BytecodeEmitter::LazyFunction) { MOZ_ASSERT(lazyScript->isBinAST()); nestedMode = BytecodeEmitter::Normal; } BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, funNode->pn_pos, nestedMode); if (!bce2.init()) { return false; } /* We measured the max scope depth when we parsed the function. */ if (!bce2.emitFunctionScript(funNode, TopLevelFunction::No)) { return false; } if (funbox->isLikelyConstructorWrapper()) { script->setLikelyConstructorWrapper(); } } if (outersc->isFunctionBox()) { outersc->asFunctionBox()->setHasInnerFunctions(); } } else { MOZ_ASSERT(IsAsmJSModule(fun)); } // Make the function object a literal in the outer script's pool. unsigned index = objectList.add(funNode->funbox()); // Non-hoisted functions simply emit their respective op. if (!funNode->functionIsHoisted()) { // JSOP_LAMBDA_ARROW is always preceded by a new.target MOZ_ASSERT(fun->isArrow() == (funNode->getOp() == JSOP_LAMBDA_ARROW)); if (funbox->isAsync()) { MOZ_ASSERT(!needsProto); return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), fun->isGenerator()); } if (fun->isArrow()) { if (sc->allowNewTarget()) { if (!emit1(JSOP_NEWTARGET)) { return false; } } else { if (!emit1(JSOP_NULL)) { return false; } } } if (needsProto) { MOZ_ASSERT(funNode->getOp() == JSOP_LAMBDA); funNode->setOp(JSOP_FUNWITHPROTO); } if (funNode->getOp() == JSOP_DEFFUN) { if (!emitIndex32(JSOP_LAMBDA, index)) { return false; } return emit1(JSOP_DEFFUN); } // This is a FunctionExpression, ArrowFunctionExpression, or class // constructor. Emit the single instruction (without location info). return emitIndex32(funNode->getOp(), index); } MOZ_ASSERT(!needsProto); bool topLevelFunction; if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { // No nested functions inside other functions are top-level. topLevelFunction = false; } else { // In sloppy eval scripts, top-level functions in are accessed // dynamically. In global and module scripts, top-level functions are // those bound in the var scope. NameLocation loc = lookupName(name); topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || loc.bindingKind() == BindingKind::Var; } if (topLevelFunction) { if (sc->isModuleContext()) { // For modules, we record the function and instantiate the binding // during ModuleInstantiate(), before the script is run. RootedModuleObject module(cx, sc->asModuleContext()->module()); if (!module->noteFunctionDeclaration(cx, name, fun)) { return false; } } else { MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); MOZ_ASSERT(funNode->getOp() == JSOP_NOP); switchToPrologue(); if (funbox->isAsync()) { if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), fun->isGenerator())) { return false; } } else { if (!emitIndex32(JSOP_LAMBDA, index)) { return false; } } if (!emit1(JSOP_DEFFUN)) { return false; } switchToMain(); } } else { // For functions nested within functions and blocks, make a lambda and // initialize the binding name of the function in the current scope. NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (funbox->isAsync()) { if (!emitAsyncWrapper(index, /* needsHomeObject = */ false, /* isArrow = */ false, funbox->isGenerator())) { return false; } } else { if (!emitIndexOp(JSOP_LAMBDA, index)) { return false; } } if (!noe.emitAssignment()) { return false; } if (!emit1(JSOP_POP)) { return false; } } return true; } bool BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) { if (isArrow) { if (sc->allowNewTarget()) { if (!emit1(JSOP_NEWTARGET)) { return false; } } else { if (!emit1(JSOP_NULL)) { return false; } } if (!emitIndex32(JSOP_LAMBDA_ARROW, index)) { return false; } } else { if (!emitIndex32(JSOP_LAMBDA, index)) { return false; } } return true; } bool BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, bool isGenerator) { // needsHomeObject can be true for propertyList for extended class. // In that case push both unwrapped and wrapped function, in order to // initialize home object of unwrapped function, and set wrapped function // as a property. // // lambda // unwrapped // dup // unwrapped unwrapped // toasync // unwrapped wrapped // // Emitted code is surrounded by the following code. // // // classObj classCtor classProto // (emitted code) // classObj classCtor classProto unwrapped wrapped // swap // classObj classCtor classProto wrapped unwrapped // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped // // initialize the home object of unwrapped // // with classProto here // pop // classObj classCtor classProto wrapped // inithiddenprop // classObj classCtor classProto wrapped // // initialize the property of the classProto // // with wrapped function here // pop // classObj classCtor classProto // // needsHomeObject is false for other cases, push wrapped function only. if (!emitAsyncWrapperLambda(index, isArrow)) { return false; } if (needsHomeObject) { if (!emit1(JSOP_DUP)) { return false; } } if (isGenerator) { if (!emit1(JSOP_TOASYNCGEN)) { return false; } } else { if (!emit1(JSOP_TOASYNC)) { return false; } } return true; } bool BytecodeEmitter::emitDo(BinaryNode* doNode) { ParseNode* bodyNode = doNode->left(); DoWhileEmitter doWhile(this); if (!doWhile.emitBody(Some(doNode->pn_pos.begin), getOffsetForLoop(bodyNode))) { return false; } if (!emitTree(bodyNode)) { return false; } if (!doWhile.emitCond()) { return false; } ParseNode* condNode = doNode->right(); if (!emitTree(condNode)) { return false; } if (!doWhile.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) { ParseNode* bodyNode = whileNode->right(); WhileEmitter wh(this); if (!wh.emitBody(Some(whileNode->pn_pos.begin), getOffsetForLoop(bodyNode), Some(whileNode->pn_pos.end))) { return false; } if (!emitTree(bodyNode)) { return false; } ParseNode* condNode = whileNode->left(); if (!wh.emitCond(getOffsetForLoop(condNode))) { return false; } if (!emitTree(condNode)) { return false; } if (!wh.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitBreak(PropertyName* label) { BreakableControl* target; SrcNoteType noteType; if (label) { // Any statement with the matching label may be the break target. auto hasSameLabel = [label](LabelControl* labelControl) { return labelControl->label() == label; }; target = findInnermostNestableControl(hasSameLabel); noteType = SRC_BREAK2LABEL; } else { auto isNotLabel = [](BreakableControl* control) { return !control->is(); }; target = findInnermostNestableControl(isNotLabel); noteType = (target->kind() == StatementKind::Switch) ? SRC_SWITCHBREAK : SRC_BREAK; } return emitGoto(target, &target->breaks, noteType); } bool BytecodeEmitter::emitContinue(PropertyName* label) { LoopControl* target = nullptr; if (label) { // Find the loop statement enclosed by the matching label. NestableControl* control = innermostNestableControl; while (!control->is() || control->as().label() != label) { if (control->is()) { target = &control->as(); } control = control->enclosing(); } } else { target = findInnermostNestableControl(); } return emitGoto(target, &target->continues, SRC_CONTINUE); } bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function); MOZ_ASSERT(thisName->isName(cx->names().dotThis)); return emitGetFunctionThis(Some(thisName->pn_pos.begin)); } bool BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe& offset) { if (offset) { if (!updateLineNumberNotes(*offset)) { return false; } } if (!emitGetName(cx->names().dotThis)) { // THIS return false; } if (sc->needsThisTDZChecks()) { if (!emit1(JSOP_CHECKTHIS)) { // THIS return false; } } return true; } bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) { MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase)); NameNode* nameNode = &superBase->kid()->as(); return emitGetFunctionThis(nameNode); // THIS } bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { if (ParseNode* kid = pn->kid()) { NameNode* thisName = &kid->as(); return emitGetFunctionThis(thisName); // THIS } if (sc->thisBinding() == ThisBinding::Module) { return emit1(JSOP_UNDEFINED); // UNDEF } MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); return emit1(JSOP_GLOBALTHIS); // THIS } bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot()); if (!emitGetName(cx->names().dotThis)) { return false; } if (!emit1(JSOP_CHECKRETURN)) { return false; } return true; } bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { return false; } bool needsIteratorResult = sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult(); if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { return false; } } /* Push a return value */ if (ParseNode* expr = returnNode->kid()) { if (!emitTree(expr)) { return false; } bool isAsyncGenerator = sc->asFunctionBox()->isAsync() && sc->asFunctionBox()->isGenerator(); if (isAsyncGenerator) { if (!emitAwaitInInnermostScope()) { return false; } } } else { /* No explicit return value provided */ if (!emit1(JSOP_UNDEFINED)) { return false; } } if (needsIteratorResult) { if (!emitFinishIteratorResult(true)) { return false; } } // We know functionBodyEndPos is set because "return" is only // valid in a function, and so we've passed through // emitFunctionScript. MOZ_ASSERT(functionBodyEndPosSet); if (!updateSourceCoordNotes(functionBodyEndPos)) { 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 needsFinalYield = sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); bool isDerivedClassConstructor = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); if (!emit1((needsFinalYield || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) { return false; } // Make sure that we emit this before popping the blocks in prepareForNonLocalJump, // to ensure that the error is thrown while the scope-chain is still intact. if (isDerivedClassConstructor) { if (!emitCheckDerivedClassConstructorReturn()) { return false; } } NonLocalExitControl nle(this, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) { return false; } if (needsFinalYield) { // We know that .generator is on the function scope, as we just exited // all nested scopes. NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, varEmitterScope); if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) { return false; } if (!emitYieldOp(JSOP_FINALYIELDRVAL)) { return false; } } else if (isDerivedClassConstructor) { MOZ_ASSERT(code()[top] == JSOP_SETRVAL); if (!emit1(JSOP_RETRVAL)) { return false; } } else if (top + static_cast(JSOP_RETURN_LENGTH) != offset()) { code()[top] = JSOP_SETRVAL; if (!emit1(JSOP_RETRVAL)) { return false; } } return true; } bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, ¤tScope); return emitGetNameAtLocation(cx->names().dotGenerator, loc); } bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) { if (!emitTree(yieldNode->kid())) { return false; } if (!emitYieldOp(JSOP_INITIALYIELD)) { return false; } if (!emit1(JSOP_POP)) { return false; } return true; } bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::Yield)); bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { return false; } } if (ParseNode* expr = yieldNode->kid()) { if (!emitTree(expr)) { return false; } } else { if (!emit1(JSOP_UNDEFINED)) { return false; } } // 11.4.3.7 AsyncGeneratorYield step 5. bool isAsyncGenerator = sc->asFunctionBox()->isAsync(); if (isAsyncGenerator) { if (!emitAwaitInInnermostScope()) { // RESULT return false; } } if (needsIteratorResult) { if (!emitFinishIteratorResult(false)) { return false; } } if (!emitGetDotGeneratorInInnermostScope()) { return false; } if (!emitYieldOp(JSOP_YIELD)) { return false; } return true; } bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::Await)); if (!emitTree(awaitNode->kid())) { return false; } return emitAwaitInInnermostScope(); } bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) { if (!emit1(JSOP_TRYSKIPAWAIT)) { // VALUE_OR_RESOLVED CANSKIP return false; } if (!emit1(JSOP_NOT)) { // VALUE_OR_RESOLVED !CANSKIP return false; } InternalIfEmitter ifCanSkip(this); if (!ifCanSkip.emitThen()) { // VALUE_OR_RESOLVED return false; } if (!emitGetDotGeneratorInScope(currentScope)) { return false; // VALUE GENERATOR } if (!emitYieldOp(JSOP_AWAIT)) { // RESOLVED return false; } if (!ifCanSkip.emitEnd()) { return false; } MOZ_ASSERT(ifCanSkip.popped() == 0); return true; } bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isGenerator()); IteratorKind iterKind = sc->asFunctionBox()->isAsync() ? IteratorKind::Async : IteratorKind::Sync; if (!emitTree(iter)) { // ITERABLE return false; } if (iterKind == IteratorKind::Async) { if (!emitAsyncIterator()) { // NEXT ITER return false; } } else { if (!emitIterator()) { // NEXT ITER return false; } } // Initial send value is undefined. if (!emit1(JSOP_UNDEFINED)) { // NEXT ITER RECEIVED return false; } int32_t savedDepthTemp; int32_t startDepth = stackDepth; MOZ_ASSERT(startDepth >= 3); TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, TryEmitter::ControlKind::NonSyntactic); if (!tryCatch.emitJumpOverCatchAndFinally()) { // NEXT ITER RESULT return false; } JumpTarget tryStart{ offset() }; if (!tryCatch.emitTry()) { // NEXT ITER RESULT return false; } MOZ_ASSERT(this->stackDepth == startDepth); // 11.4.3.7 AsyncGeneratorYield step 5. if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // NEXT ITER RESULT return false; } } // Load the generator object. if (!emitGetDotGeneratorInInnermostScope()) { // NEXT ITER RESULT GENOBJ return false; } // Yield RESULT as-is, without re-boxing. if (!emitYieldOp(JSOP_YIELD)) { // NEXT ITER RECEIVED return false; } if (!tryCatch.emitCatch()) { // NEXT ITER RESULT return false; } MOZ_ASSERT(stackDepth == startDepth); if (!emit1(JSOP_EXCEPTION)) { // NEXT ITER RESULT EXCEPTION return false; } if (!emitDupAt(2)) { // NEXT ITER RESULT EXCEPTION ITER return false; } if (!emit1(JSOP_DUP)) { // NEXT ITER RESULT EXCEPTION ITER ITER return false; } if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) { // NEXT ITER RESULT EXCEPTION ITER THROW return false; } savedDepthTemp = stackDepth; InternalIfEmitter ifThrowMethodIsNotDefined(this); if (!emitPushNotUndefinedOrNull()) { // NEXT ITER RESULT EXCEPTION ITER THROW NOT-UNDEF-OR-NULL return false; } if (!ifThrowMethodIsNotDefined.emitThenElse()) { // NEXT ITER RESULT EXCEPTION ITER THROW return false; } // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4. // RESULT = ITER.throw(EXCEPTION) // NEXT ITER OLDRESULT EXCEPTION ITER THROW if (!emit1(JSOP_SWAP)) { // NEXT ITER OLDRESULT EXCEPTION THROW ITER return false; } if (!emit2(JSOP_PICK, 2)) { // NEXT ITER OLDRESULT THROW ITER EXCEPTION return false; } if (!emitCall(JSOP_CALL, 1, iter)) { // NEXT ITER OLDRESULT RESULT return false; } checkTypeSet(JSOP_CALL); if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // NEXT ITER OLDRESULT RESULT return false; } } if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { // NEXT ITER OLDRESULT RESULT return false; } if (!emit1(JSOP_SWAP)) { // NEXT ITER RESULT OLDRESULT return false; } if (!emit1(JSOP_POP)) { // NEXT ITER RESULT return false; } MOZ_ASSERT(this->stackDepth == startDepth); JumpList checkResult; // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii. // // Note that there is no GOSUB to the finally block here. If the iterator has a // "throw" method, it does not perform IteratorClose. if (!emitJump(JSOP_GOTO, &checkResult)) { // goto checkResult return false; } stackDepth = savedDepthTemp; if (!ifThrowMethodIsNotDefined.emitElse()) { // NEXT ITER RESULT EXCEPTION ITER THROW return false; } if (!emit1(JSOP_POP)) { // NEXT ITER RESULT EXCEPTION ITER return false; } // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2 // // If the iterator does not have a "throw" method, it calls IteratorClose // and then throws a TypeError. if (!emitIteratorCloseInInnermostScope(iterKind)) { // NEXT ITER RESULT EXCEPTION return false; } if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) { // throw return false; } stackDepth = savedDepthTemp; if (!ifThrowMethodIsNotDefined.emitEnd()) { return false; } stackDepth = startDepth; if (!tryCatch.emitFinally()) { return false; } // ES 14.4.13, yield * AssignmentExpression, step 5.c // // Call iterator.return() for receiving a "forced return" completion from // the generator. InternalIfEmitter ifGeneratorClosing(this); if (!emit1(JSOP_ISGENCLOSING)) { // NEXT ITER RESULT FTYPE FVALUE CLOSING return false; } if (!ifGeneratorClosing.emitThen()) { // NEXT ITER RESULT FTYPE FVALUE return false; } // Step ii. // // Get the "return" method. if (!emitDupAt(3)) { // NEXT ITER RESULT FTYPE FVALUE ITER return false; } if (!emit1(JSOP_DUP)) { // NEXT ITER RESULT FTYPE FVALUE ITER ITER return false; } if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) { // NEXT ITER RESULT FTYPE FVALUE ITER RET return false; } // Step iii. // // Do nothing if "return" is undefined or null. InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) { // NEXT ITER RESULT FTYPE FVALUE ITER RET NOT-UNDEF-OR-NULL return false; } // Step iv. // // Call "return" with the argument passed to Generator.prototype.return, // which is currently in rval.value. if (!ifReturnMethodIsDefined.emitThenElse()) { // NEXT ITER OLDRESULT FTYPE FVALUE ITER RET return false; } if (!emit1(JSOP_SWAP)) { // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER return false; } if (!emit1(JSOP_GETRVAL)) { // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER RVAL return false; } if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER VALUE return false; } if (!emitCall(JSOP_CALL, 1)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT return false; } checkTypeSet(JSOP_CALL); if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // ... FTYPE FVALUE RESULT return false; } } // Step v. if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT return false; } // Steps vi-viii. // // Check if the returned object from iterator.return() is done. If not, // continuing yielding. InternalIfEmitter ifReturnDone(this); if (!emit1(JSOP_DUP)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT RESULT return false; } if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT DONE return false; } if (!ifReturnDone.emitThenElse()) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT return false; } if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // NEXT ITER OLDRESULT FTYPE FVALUE VALUE return false; } if (!emitPrepareIteratorResult()) { // NEXT ITER OLDRESULT FTYPE FVALUE VALUE RESULT return false; } if (!emit1(JSOP_SWAP)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT VALUE return false; } if (!emitFinishIteratorResult(true)) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT return false; } if (!emit1(JSOP_SETRVAL)) { // NEXT ITER OLDRESULT FTYPE FVALUE return false; } savedDepthTemp = this->stackDepth; if (!ifReturnDone.emitElse()) { // NEXT ITER OLDRESULT FTYPE FVALUE RESULT return false; } if (!emit2(JSOP_UNPICK, 3)) { // NEXT ITER RESULT OLDRESULT FTYPE FVALUE return false; } if (!emitPopN(3)) { // NEXT ITER RESULT return false; } { // goto tryStart; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) { // NEXT ITER RESULT return false; } } this->stackDepth = savedDepthTemp; if (!ifReturnDone.emitEnd()) { return false; } if (!ifReturnMethodIsDefined.emitElse()) { // NEXT ITER RESULT FTYPE FVALUE ITER RET return false; } if (!emitPopN(2)) { // NEXT ITER RESULT FTYPE FVALUE return false; } if (!ifReturnMethodIsDefined.emitEnd()) { return false; } if (!ifGeneratorClosing.emitEnd()) { return false; } if (!tryCatch.emitEnd()) { return false; } // After the try-catch-finally block: send the received value to the iterator. // result = iter.next(received) // NEXT ITER RECEIVED if (!emit2(JSOP_UNPICK, 2)) { // RECEIVED NEXT ITER return false; } if (!emit1(JSOP_DUP2)) { // RECEIVED NEXT ITER NEXT ITER return false; } if (!emit2(JSOP_PICK, 4)) { // NEXT ITER NEXT ITER RECEIVED return false; } if (!emitCall(JSOP_CALL, 1, iter)) { // NEXT ITER RESULT return false; } checkTypeSet(JSOP_CALL); if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // NEXT ITER RESULT RESULT return false; } } if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { // NEXT ITER RESULT return false; } MOZ_ASSERT(this->stackDepth == startDepth); if (!emitJumpTargetAndPatch(checkResult)) { // checkResult: return false; } // if (!result.done) goto tryStart; // NEXT ITER RESULT if (!emit1(JSOP_DUP)) { // NEXT ITER RESULT RESULT return false; } if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) { // NEXT ITER RESULT DONE return false; } // if (!DONE) goto tryStart; { JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) { // NEXT ITER RESULT return false; } } // result.value if (!emit2(JSOP_UNPICK, 2)) { // RESULT NEXT ITER return false; } if (!emitPopN(2)) { // RESULT return false; } if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) { // VALUE return false; } MOZ_ASSERT(this->stackDepth == startDepth - 2); return true; } bool BytecodeEmitter::emitStatementList(ListNode* stmtList) { for (ParseNode* stmt : stmtList->contents()) { if (!emitTree(stmt)) { return false; } } return true; } bool BytecodeEmitter::emitExpressionStatement(UnaryNode* exprStmt) { MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStatement)); /* * 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. */ ParseNode* expr = exprStmt->kid(); if (!useful) { if (!checkSideEffects(expr, &useful)) { return false; } /* * Don't eliminate apparently useless expressions if they are labeled * expression statements. The startOffset() test catches the case * where we are nesting in emitTree for a labeled compound statement. */ if (innermostNestableControl && innermostNestableControl->is() && innermostNestableControl->as().startOffset() >= offset()) { useful = true; } } if (useful) { MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::Assign), expr->isOp(JSOP_NOP)); ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; ExpressionStatementEmitter ese(this, valueUsage); if (!ese.prepareForExpr(Some(exprStmt->pn_pos.begin))) { return false; } if (!emitTree(expr, valueUsage)) { return false; } if (!ese.emitEnd()) { return false; } } else if (exprStmt->isDirectivePrologueMember()) { // Don't complain about directive prologue members; just don't emit // their code. } else { if (JSAtom* atom = exprStmt->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()) { if (IsAsmJSModule(sc->asFunctionBox()->function())) { directive = js_useAsm_str; } } } if (directive) { if (!reportExtraWarning(expr, JSMSG_CONTRARY_NONDIRECTIVE, directive)) { return false; } } } else { if (!reportExtraWarning(expr, JSMSG_USELESS_EXPR)) { return false; } } } return true; } bool BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteName)); NameNode* nameExpr = &deleteNode->kid()->as(); MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); return emitAtomOp(nameExpr->atom(), JSOP_DELNAME); } bool BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteProp)); PropertyAccess* propExpr = &deleteNode->kid()->as(); // TODO(khyperia): Implement private field access. PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, propExpr->as().isSuper() ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (propExpr->isSuper()) { // The expression |delete super.foo;| has to evaluate |super.foo|, // which could throw if |this| hasn't yet been set by a |super(...)| // call or the super-base is not an object, before throwing a // ReferenceError for attempting to delete a super-reference. UnaryNode* base = &propExpr->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } } else { if (!poe.prepareForObj()) { return false; } if (!emitPropLHS(propExpr)) { // OBJ return false; } } if (!poe.emitDelete(propExpr->key().atom())) { // [Super] // // THIS // // [Other] // // SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElem)); PropertyByValue* elemExpr = &deleteNode->kid()->as(); bool isSuper = elemExpr->isSuper(); ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (isSuper) { // The expression |delete super[foo];| has to evaluate |super[foo]|, // which could throw if |this| hasn't yet been set by a |super(...)| // call, or trigger side-effects when evaluating ToPropertyKey(foo), // or also throw when the super-base is not an object, before throwing // a ReferenceError for attempting to delete a super-reference. if (!eoe.prepareForObj()) { // return false; } UnaryNode* base = &elemExpr->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } if (!eoe.prepareForKey()) { // THIS return false; } if (!emitTree(&elemExpr->key())) { // THIS KEY return false; } } else { if (!emitElemObjAndKey(elemExpr, false, eoe)) { // OBJ KEY return false; } } if (!eoe.emitDelete()) { // [Super] // // THIS // // [Other] // // SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr)); ParseNode* expression = deleteNode->kid(); // If useless, just emit JSOP_TRUE; otherwise convert |delete | to // effectively |, true|. bool useful = false; if (!checkSideEffects(expression, &useful)) { return false; } if (useful) { if (!emitTree(expression)) { return false; } if (!emit1(JSOP_POP)) { return false; } } return emit1(JSOP_TRUE); } static const char * SelfHostedCallFunctionName(JSAtom* name, JSContext* cx) { if (name == cx->names().callFunction) { return "callFunction"; } if (name == cx->names().callContentFunction) { return "callContentFunction"; } if (name == cx->names().constructContentFunction) { return "constructContentFunction"; } MOZ_CRASH("Unknown self-hosted call function name"); } bool BytecodeEmitter::emitSelfHostedCallFunction(BinaryNode* callNode) { // 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. NameNode* calleeNode = &callNode->left()->as(); ListNode* argsList = &callNode->right()->as(); const char* errorName = SelfHostedCallFunctionName(calleeNode->name(), cx); if (argsList->count() < 2) { reportError(callNode, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s"); return false; } JSOp callOp = callNode->getOp(); if (callOp != JSOP_CALL) { reportError(callNode, JSMSG_NOT_CONSTRUCTOR, errorName); return false; } bool constructing = calleeNode->name() == cx->names().constructContentFunction; ParseNode* funNode = argsList->head(); if (constructing) { callOp = JSOP_NEW; } else if (funNode->isName(cx->names().std_Function_apply)) { callOp = JSOP_FUNAPPLY; } if (!emitTree(funNode)) { return false; } #ifdef DEBUG if (emitterMode == BytecodeEmitter::SelfHosting && calleeNode->name() == cx->names().callFunction) { if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) { return false; } } #endif ParseNode* thisOrNewTarget = funNode->pn_next; if (constructing) { // Save off the new.target value, but here emit a proper |this| for a // constructing call. if (!emit1(JSOP_IS_CONSTRUCTING)) { return false; } } else { // It's |this|, emit it. if (!emitTree(thisOrNewTarget)) { return false; } } for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; argpn = argpn->pn_next) { if (!emitTree(argpn)) { return false; } } if (constructing) { if (!emitTree(thisOrNewTarget)) { return false; } } uint32_t argc = argsList->count() - 2; if (!emitCall(callOp, argc)) { return false; } checkTypeSet(callOp); return true; } bool BytecodeEmitter::emitSelfHostedResumeGenerator(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') if (argsList->count() != 3) { reportError(callNode, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s"); return false; } ParseNode* genNode = argsList->head(); if (!emitTree(genNode)) { return false; } ParseNode* valNode = genNode->pn_next; if (!emitTree(valNode)) { return false; } ParseNode* kindNode = valNode->pn_next; MOZ_ASSERT(kindNode->isKind(ParseNodeKind::String)); uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->as().atom()); MOZ_ASSERT(!kindNode->pn_next); if (!emitCall(JSOP_RESUME, operand)) { return false; } return true; } bool BytecodeEmitter::emitSelfHostedForceInterpreter() { if (!emit1(JSOP_FORCEINTERPRETER)) { return false; } if (!emit1(JSOP_UNDEFINED)) { return false; } return true; } bool BytecodeEmitter::emitSelfHostedAllowContentIter(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 1) { reportError(callNode, JSMSG_MORE_ARGS_NEEDED, "allowContentIter", "1", ""); return false; } // We're just here as a sentinel. Pass the value through directly. return emitTree(argsList->head()); } bool BytecodeEmitter::emitSelfHostedDefineDataProperty(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); // Only optimize when 3 arguments are passed. MOZ_ASSERT(argsList->count() == 3); ParseNode* objNode = argsList->head(); if (!emitTree(objNode)) { return false; } ParseNode* idNode = objNode->pn_next; if (!emitTree(idNode)) { return false; } ParseNode* valNode = idNode->pn_next; if (!emitTree(valNode)) { return false; } // This will leave the object on the stack instead of pushing |undefined|, // but that's fine because the self-hosted code doesn't use the return // value. return emit1(JSOP_INITELEM); } bool BytecodeEmitter::emitSelfHostedHasOwn(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 2) { reportError(callNode, JSMSG_MORE_ARGS_NEEDED, "hasOwn", "2", ""); return false; } ParseNode* idNode = argsList->head(); if (!emitTree(idNode)) { return false; } ParseNode* objNode = idNode->pn_next; if (!emitTree(objNode)) { return false; } return emit1(JSOP_HASOWN); } bool BytecodeEmitter::emitSelfHostedGetPropertySuper(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 3) { reportError(callNode, JSMSG_MORE_ARGS_NEEDED, "getPropertySuper", "3", ""); return false; } ParseNode* objNode = argsList->head(); ParseNode* idNode = objNode->pn_next; ParseNode* receiverNode = idNode->pn_next; if (!emitTree(receiverNode)) { return false; } if (!emitTree(idNode)) { return false; } if (!emitTree(objNode)) { return false; } return emitElemOpBase(JSOP_GETELEM_SUPER); } bool BytecodeEmitter::isRestParameter(ParseNode* expr) { if (!sc->isFunctionBox()) { return false; } FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); if (!funbox->hasRest()) { return false; } if (!expr->isKind(ParseNodeKind::Name)) { if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(ParseNodeKind::Call)) { BinaryNode* callNode = &expr->as(); ParseNode* calleeNode = callNode->left(); if (calleeNode->isName(cx->names().allowContentIter)) { return isRestParameter(callNode->right()->as().head()); } } return false; } JSAtom* name = expr->as().name(); Maybe paramLoc = locationOfNameBoundInFunctionScope(name); if (paramLoc && lookupName(name) == *paramLoc) { FunctionScope::Data* bindings = funbox->functionScopeBindings(); if (bindings->nonPositionalFormalStart > 0) { // |paramName| can be nullptr when the rest destructuring syntax is // used: `function f(...[]) {}`. JSAtom* paramName = bindings->trailingNames[bindings->nonPositionalFormalStart - 1].name(); return paramName && name == paramName; } } return false; } bool BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone) { switch (callee->getKind()) { case ParseNodeKind::Name: if (!cone.emitNameCallee(callee->as().name())) { return false; // CALLEE THIS } break; case ParseNodeKind::Dot: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyAccess* prop = &callee->as(); // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } } else { if (!emitPropLHS(prop)) { // OBJ return false; } } if (!poe.emitGet(prop->key().atom())) { // CALLEE THIS? return false; } break; } case ParseNodeKind::Elem: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyByValue* elem = &callee->as(); bool isSuper = elem->isSuper(); ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] // // THIS? THIS KEY // // [needsThis,Other] // // OBJ? OBJ KEY return false; } if (!eoe.emitGet()) { // CALLEE? THIS return false; } break; } case ParseNodeKind::Function: if (!cone.prepareForFunctionCallee()) { return false; } if (!emitTree(callee)) { // CALLEE return false; } break; case ParseNodeKind::SuperBase: MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall)); MOZ_ASSERT(parser->astGenerator().isSuperBase(callee)); if (!cone.emitSuperCallee()) { // CALLEE THIS return false; } break; default: if (!cone.prepareForOtherCallee()) { return false; } if (!emitTree(callee)) { return false; } break; } if (!cone.emitThis()) { // CALLEE THIS return false; } return true; } bool BytecodeEmitter::emitPipeline(ListNode* node) { MOZ_ASSERT(node->count() >= 2); if (!emitTree(node->head())) { // ARG return false; } ParseNode* callee = node->head()->pn_next; CallOrNewEmitter cone(this, JSOP_CALL, CallOrNewEmitter::ArgumentsKind::Other, ValueUsage::WantValue); do { if (!emitCalleeAndThis(callee, node, cone)) { // ARG CALLEE THIS return false; } if (!emit2(JSOP_PICK, 2)) { // CALLEE THIS ARG return false; } if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { // RVAL return false; } cone.reset(); checkTypeSet(JSOP_CALL); } while ((callee = callee->pn_next)); return true; } bool BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, bool isSpread, CallOrNewEmitter& cone) { uint32_t argc = argsList->count(); if (argc >= ARGC_LIMIT) { reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); return false; } if (!isSpread) { if (!cone.prepareForNonSpreadArguments()) { // CALLEE THIS return false; } for (ParseNode* arg : argsList->contents()) { if (!emitTree(arg)) { return false; } } } else { if (cone.wantSpreadOperand()) { UnaryNode* spreadNode = &argsList->head()->as(); if (!emitTree(spreadNode->kid())) { // CALLEE THIS ARG0 return false; } } if (!cone.emitSpreadArgumentsTest()) { // CALLEE THIS return false; } if (!emitArray(argsList->head(), argc)) { // CALLEE THIS ARR return false; } } return true; } bool BytecodeEmitter::emitCallOrNew(BinaryNode* callNode, ValueUsage valueUsage /* = ValueUsage::WantValue */) { /* * 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). */ bool isCall = callNode->isKind(ParseNodeKind::Call) || callNode->isKind(ParseNodeKind::TaggedTemplate); ParseNode* calleeNode = callNode->left(); ListNode* argsList = &callNode->right()->as(); bool isSpread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE; if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting && !isSpread) { // Calls to "forceInterpreter", "callFunction", // "callContentFunction", or "resumeGenerator" in self-hosted // code generate inline bytecode. PropertyName* calleeName = calleeNode->as().name(); if (calleeName == cx->names().callFunction || calleeName == cx->names().callContentFunction || calleeName == cx->names().constructContentFunction) { return emitSelfHostedCallFunction(callNode); } if (calleeName == cx->names().resumeGenerator) { return emitSelfHostedResumeGenerator(callNode); } if (calleeName == cx->names().forceInterpreter) { return emitSelfHostedForceInterpreter(); } if (calleeName == cx->names().allowContentIter) { return emitSelfHostedAllowContentIter(callNode); } if (calleeName == cx->names().defineDataPropertyIntrinsic && argsList->count() == 3) { return emitSelfHostedDefineDataProperty(callNode); } if (calleeName == cx->names().hasOwn) { return emitSelfHostedHasOwn(callNode); } if (calleeName == cx->names().getPropertySuper) { return emitSelfHostedGetPropertySuper(callNode); } // Fall through } JSOp op = callNode->getOp(); uint32_t argc = argsList->count(); CallOrNewEmitter cone(this, op, isSpread && (argc == 1) && isRestParameter(argsList->head()->as().kid()) ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest : CallOrNewEmitter::ArgumentsKind::Other, valueUsage); if (!emitCalleeAndThis(calleeNode, callNode, cone)) { // CALLEE THIS return false; } if (!emitArguments(argsList, isCall, isSpread, cone)) { return false; // CALLEE THIS ARGS... } ParseNode* coordNode = callNode; if (op == JSOP_CALL || op == JSOP_SPREADCALL) { switch (calleeNode->getKind()) { case ParseNodeKind::Dot: { // Check if this member is a simple chain of simple chain of // property accesses, e.g. x.y.z, this.x.y, super.x.y bool simpleDotChain = false; for (ParseNode* cur = calleeNode; cur->isKind(ParseNodeKind::Dot); cur = &cur->as().expression()) { PropertyAccess* prop = &cur->as(); ParseNode* left = &prop->expression(); // TODO(khyperia): Implement private field access. if (left->isKind(ParseNodeKind::Name) || left->isKind(ParseNodeKind::This) || left->isKind(ParseNodeKind::SuperBase)) { simpleDotChain = true; } } if (!simpleDotChain) { // obj().aprop() // expression // ^ // column coord // // Note: Because of the constant folding logic in FoldElement, // this case also applies for constant string properties. // // obj()['aprop']() // expression // ^ // column coord coordNode = &calleeNode->as().key(); } break; } case ParseNodeKind::Elem: // obj[expr]() // expression // ^ // column coord coordNode = argsList; break; default: break; } } if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { return false; // RVAL } return true; } static const JSOp ParseNodeKindToJSOp[] = { // JSOP_NOP is for pipeline operator which does not emit its own JSOp // but has highest precedence in binary operators JSOP_NOP, JSOP_OR, JSOP_AND, JSOP_BITOR, JSOP_BITXOR, JSOP_BITAND, JSOP_STRICTEQ, JSOP_EQ, JSOP_STRICTNE, JSOP_NE, JSOP_LT, JSOP_LE, JSOP_GT, JSOP_GE, JSOP_INSTANCEOF, JSOP_IN, JSOP_LSH, JSOP_RSH, JSOP_URSH, JSOP_ADD, JSOP_SUB, JSOP_MUL, JSOP_DIV, JSOP_MOD, JSOP_POW }; static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst); MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast); return ParseNodeKindToJSOp[size_t(pnk) - size_t(ParseNodeKind::BinOpFirst)]; } bool BytecodeEmitter::emitRightAssociative(ListNode* node) { // ** is the only right-associative operator. MOZ_ASSERT(node->isKind(ParseNodeKind::Pow)); // Right-associative operator chain. for (ParseNode* subexpr : node->contents()) { if (!emitTree(subexpr)) { return false; } } for (uint32_t i = 0; i < node->count() - 1; i++) { if (!emit1(JSOP_POW)) { return false; } } return true; } bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { // Left-associative operator chain. if (!emitTree(node->head())) { return false; } JSOp op = BinaryOpParseNodeKindToJSOp(node->getKind()); ParseNode* nextExpr = node->head()->pn_next; do { if (!emitTree(nextExpr)) { return false; } if (!emit1(op)) { return false; } } while ((nextExpr = nextExpr->pn_next)); return true; } bool BytecodeEmitter::emitLogical(ListNode* node) { MOZ_ASSERT(node->isKind(ParseNodeKind::Or) || node->isKind(ParseNodeKind::And)); /* * 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. */ TDZCheckCache tdzCache(this); /* Left-associative operator chain: avoid too much recursion. */ ParseNode* expr = node->head(); if (!emitTree(expr)) { return false; } JSOp op = node->isKind(ParseNodeKind::Or) ? JSOP_OR : JSOP_AND; JumpList jump; if (!emitJump(op, &jump)) { return false; } if (!emit1(JSOP_POP)) { return false; } /* Emit nodes between the head and the tail. */ while ((expr = expr->pn_next)->pn_next) { if (!emitTree(expr)) { return false; } if (!emitJump(op, &jump)) { return false; } if (!emit1(JSOP_POP)) { return false; } } if (!emitTree(expr)) { return false; } if (!emitJumpTargetAndPatch(jump)) { return false; } return true; } bool BytecodeEmitter::emitSequenceExpr(ListNode* node, ValueUsage valueUsage /* = ValueUsage::WantValue */) { for (ParseNode* child = node->head(); ; child = child->pn_next) { if (!updateSourceCoordNotes(child->pn_pos.begin)) { return false; } if (!emitTree(child, child->pn_next ? ValueUsage::IgnoreValue : valueUsage)) { 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(UnaryNode* incDec) { switch (incDec->kid()->getKind()) { case ParseNodeKind::Dot: return emitPropIncDec(incDec); case ParseNodeKind::Elem: return emitElemIncDec(incDec); case ParseNodeKind::Call: return emitCallIncDec(incDec); default: return emitNameIncDec(incDec); } } // 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. */ uint32_t index; if (!makeAtomIndex(pn->label(), &index)) { return false; } JumpList top; if (!emitJump(JSOP_LABEL, &top)) { return false; } /* Emit code for the labeled statement. */ LabelControl controlInfo(this, pn->label(), offset()); if (!emitTree(pn->statement())) { return false; } /* Patch the JSOP_LABEL offset. */ JumpTarget brk{ lastNonJumpTargetOffset() }; patchJumpsToTarget(top, brk); if (!controlInfo.patchBreaks(this)) { return false; } return true; } bool BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, ValueUsage valueUsage /* = ValueUsage::WantValue */) { CondEmitter cond(this); if (!cond.emitCond()) { return false; } if (!emitTree(&conditional.condition())) { return false; } if (!cond.emitThenElse()) { return false; } if (!emitTree(&conditional.thenExpression(), valueUsage)) { return false; } if (!cond.emitElse()) { return false; } if (!emitTree(&conditional.elseExpression(), valueUsage)) { return false; } if (!cond.emitEnd()) { return false; } MOZ_ASSERT(cond.pushed() == 1); return true; } bool BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) { for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { // TODO(khyperia): Implement private field access. return false; } 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(ParseNodeKind::MutateProto)) { MOZ_ASSERT(type == ObjectLiteral); if (!emitTree(propdef->as().kid())) { return false; } objp.set(nullptr); if (!emit1(JSOP_MUTATEPROTO)) { return false; } continue; } if (propdef->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(type == ObjectLiteral); if (!emit1(JSOP_DUP)) { return false; } if (!emitTree(propdef->as().kid())) { return false; } if (!emitCopyDataProperties(CopyOption::Unfiltered)) { return false; } objp.set(nullptr); continue; } bool extraPop = false; if (type == ClassBody && propdef->as().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->as().left(); bool isIndex = false; if (key->isKind(ParseNodeKind::Number)) { if (!emitNumberOp(key->as().value())) { return false; } isIndex = true; } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String)) { // EmitClass took care of constructor already. if (type == ClassBody && key->as().atom() == cx->names().constructor && !propdef->as().isStatic()) { continue; } } else { MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); if (!emitComputedPropertyName(&key->as())) { return false; } isIndex = true; } /* Emit code for the property initializer. */ ParseNode* propVal = propdef->as().right(); if (!emitTree(propVal)) { return false; } JSOp op = propdef->getOp(); MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set : FunctionPrefixKind::None; if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) { objp.set(nullptr); } if (propVal->isKind(ParseNodeKind::Function) && propVal->as().funbox()->needsHomeObject()) { FunctionBox* funbox = propVal->as().funbox(); MOZ_ASSERT(funbox->function()->allowSuperProperty()); bool isAsync = funbox->isAsync(); if (isAsync) { if (!emit1(JSOP_SWAP)) { return false; } } if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) { return false; } if (isAsync) { if (!emit1(JSOP_POP)) { 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 (propVal->isDirectRHSAnonFunction()) { if (!emitDupAt(1)) { return false; } if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { return false; } } if (!emit1(op)) { return false; } } else { MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::String)); uint32_t index; if (!makeAtomIndex(key->as().atom(), &index)) { return false; } if (objp) { MOZ_ASSERT(type == ObjectLiteral); MOZ_ASSERT(!IsHiddenInitOp(op)); MOZ_ASSERT(!objp->inDictionaryMode()); Rooted id(cx, AtomToId(key->as().atom())); if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue, JSPROP_ENUMERATE)) { return false; } if (objp->inDictionaryMode()) { objp.set(nullptr); } } if (propVal->isDirectRHSAnonFunction()) { MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); RootedAtom keyName(cx, key->as().atom()); if (!setOrEmitSetFunName(propVal, keyName)) { return false; } } 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(ListNode* objNode) { if (!objNode->hasNonConstInitializer() && objNode->head() && checkSingletonContext()) { return emitSingletonInitialiser(objNode); } /* * 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()) { 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. // In the case of computed property names and indices, we cannot fix the // shape at bytecode compile time. When the shape cannot be determined, // |obj| is nulled out. // No need to do any guessing for the object kind, since we know the upper // bound of how many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(objNode->count()); RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); if (!obj) { return false; } if (!emitPropertyList(objNode, &obj, ObjectLiteral)) { return false; } if (obj) { // The object survived and has a predictable shape: update the original // bytecode. if (!replaceNewInitWithNewObject(obj, offset)) { return false; } } return true; } bool BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset) { 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); MOZ_ASSERT(code[0] == JSOP_NEWINIT); code[0] = JSOP_NEWOBJECT; SET_UINT32(code, index); return true; } bool BytecodeEmitter::emitArrayLiteral(ListNode* array) { if (!array->hasNonConstInitializer() && array->head()) { if (checkSingletonContext()) { // Bake in the object entirely if it will only be created once. return emitSingletonInitialiser(array); } // 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. Don't do this if the array is // small: copying the elements lazily is not worth it in that case. static const size_t MinElementsForCopyOnWrite = 5; if (emitterMode != BytecodeEmitter::SelfHosting && array->count() >= MinElementsForCopyOnWrite) { RootedValue value(cx); if (!array->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() && obj->as().denseElementsAreCopyOnWrite()); ObjectBox* objbox = parser->newObjectBox(obj); if (!objbox) { return false; } return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE); } } } return emitArray(array->head(), array->count()); } bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) { /* * 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. */ uint32_t nspread = 0; for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { if (elem->isKind(ParseNodeKind::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(JSOP_NEWARRAY, count - nspread)) { // ARRAY return false; } ParseNode* elem = arrayHead; uint32_t index; bool afterSpread = false; for (index = 0; elem; index++, elem = elem->pn_next) { if (!afterSpread && elem->isKind(ParseNodeKind::Spread)) { afterSpread = true; if (!emitNumberOp(index)) { // ARRAY INDEX return false; } } if (!updateSourceCoordNotes(elem->pn_pos.begin)) { return false; } bool allowSelfHostedIter = false; if (elem->isKind(ParseNodeKind::Elision)) { if (!emit1(JSOP_HOLE)) { return false; } } else { ParseNode* expr; if (elem->isKind(ParseNodeKind::Spread)) { expr = elem->as().kid(); if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(ParseNodeKind::Call) && expr->as().left()->isName(cx->names().allowContentIter)) { allowSelfHostedIter = true; } } else { expr = elem; } if (!emitTree(expr)) { // ARRAY INDEX? VALUE return false; } } if (elem->isKind(ParseNodeKind::Spread)) { if (!emitIterator()) { // ARRAY INDEX NEXT ITER return false; } if (!emit2(JSOP_PICK, 3)) { // INDEX NEXT ITER ARRAY return false; } if (!emit2(JSOP_PICK, 3)) { // NEXT ITER ARRAY INDEX return false; } if (!emitSpread(allowSelfHostedIter)) { // 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; } static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { switch (pnk) { case ParseNodeKind::Throw: return JSOP_THROW; case ParseNodeKind::Void: return JSOP_VOID; case ParseNodeKind::Not: return JSOP_NOT; case ParseNodeKind::BitNot: return JSOP_BITNOT; case ParseNodeKind::Pos: return JSOP_POS; case ParseNodeKind::Neg: return JSOP_NEG; default: MOZ_CRASH("unexpected unary op"); } } bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) { if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) { return false; } if (!emitTree(unaryNode->kid())) { return false; } return emit1(UnaryOpParseNodeKindToJSOp(unaryNode->getKind())); } bool BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) { MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR); if (!updateSourceCoordNotes(typeofNode->pn_pos.begin)) { return false; } if (!emitTree(typeofNode->kid())) { return false; } return emit1(op); } bool BytecodeEmitter::emitFunctionFormalParametersAndBody(ListNode* paramsBody) { MOZ_ASSERT(paramsBody->isKind(ParseNodeKind::ParamsBody)); ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); TDZCheckCache tdzCache(this); if (funbox->hasParameterExprs) { EmitterScope funEmitterScope(this); if (!funEmitterScope.enterFunction(this, funbox)) { return false; } if (!emitInitializeFunctionSpecialNames()) { return false; } if (!emitFunctionFormalParameters(paramsBody)) { return false; } { Maybe extraVarEmitterScope; if (funbox->hasExtraBodyVarScope()) { extraVarEmitterScope.emplace(this); if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) { return false; } // After emitting expressions for all parameters, copy over any // formal parameters which have been redeclared as vars. For // example, in the following, the var y in the body scope is 42: // // function f(x, y = 42) { var y; } // RootedAtom name(cx); if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { name = bi.name(); // There may not be a var binding of the same name. if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) { continue; } // The '.this' and '.generator' function special // bindings should never appear in the extra var // scope. 'arguments', however, may. MOZ_ASSERT(name != cx->names().dotThis && name != cx->names().dotGenerator); NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); if (!emitGetNameAtLocation(name, paramLoc)) { return false; } if (!noe.emitAssignment()) { return false; } if (!emit1(JSOP_POP)) { return false; } } } } if (!emitFunctionBody(funBody)) { return false; } if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) { return false; } } return funEmitterScope.leave(this); } // No parameter expressions. Enter the function body scope and emit // everything. // // One caveat is that Debugger considers ops in the prologue to be // unreachable (i.e. cannot set a breakpoint on it). If there are no // parameter exprs, any unobservable environment ops (like pushing the // call object, setting '.this', etc) need to go in the prologue, else it // messes up breakpoint tests. EmitterScope emitterScope(this); switchToPrologue(); if (!emitterScope.enterFunction(this, funbox)) { return false; } if (!emitInitializeFunctionSpecialNames()) { return false; } switchToMain(); if (!emitFunctionFormalParameters(paramsBody)) { return false; } if (!emitFunctionBody(funBody)) { return false; } return emitterScope.leave(this); } bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); EmitterScope* funScope = innermostEmitterScope(); bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); uint16_t argSlot = 0; for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next, argSlot++) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; if (arg->isKind(ParseNodeKind::Assign)) { bindingElement = arg->as().left(); initializer = arg->as().right(); } // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) || bindingElement->isKind(ParseNodeKind::Array) || bindingElement->isKind(ParseNodeKind::Object)); // The rest parameter doesn't have an initializer. bool isRest = hasRest && arg->pn_next == funBody; MOZ_ASSERT_IF(isRest, !initializer); bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name); // ES 14.1.19 says if BindingElement contains an expression in the // production FormalParameter : BindingElement, it is evaluated in a // new var environment. This is needed to prevent vars from escaping // direct eval in parameter expressions. Maybe paramExprVarScope; if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { paramExprVarScope.emplace(this); if (!paramExprVarScope->enterParameterExpressionVar(this)) { return false; } } // First push the RHS if there is a default expression or if it is // rest. if (initializer) { // If we have an initializer, emit the initializer and assign it // to the argument slot. TDZ is taken care of afterwards. MOZ_ASSERT(hasParameterExprs); if (!emitArgOp(JSOP_GETARG, argSlot)) { // ARG return false; } if (!emitDefault(initializer, bindingElement)) { return false; // ARG/DEFAULT } } else if (isRest) { if (!emit1(JSOP_REST)) { return false; } checkTypeSet(JSOP_REST); } // Initialize the parameter name. if (isDestructuring) { // If we had an initializer or the rest parameter, the value is // already on the stack. if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) { return false; } // If there's an parameter expression var scope, the destructuring // declaration needs to initialize the name in the function scope, // which is not the innermost scope. if (!emitDestructuringOps(&bindingElement->as(), paramExprVarScope ? DestructuringFormalParameterInVarScope : DestructuringDeclaration)) { return false; } if (!emit1(JSOP_POP)) { return false; } } else if (hasParameterExprs || isRest) { RootedAtom paramName(cx, bindingElement->as().name()); NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (hasParameterExprs) { // If we had an initializer or a rest parameter, the value is // already on the stack. if (!initializer && !isRest) { if (!emitArgOp(JSOP_GETARG, argSlot)) { return false; } } } if (!noe.emitAssignment()) { return false; } if (!emit1(JSOP_POP)) { return false; } } if (paramExprVarScope) { if (!paramExprVarScope->leave(this)) { return false; } } } return true; } bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) { // A special name must be slotful, either on the frame or on the // call environment. MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (!bce->emit1(op)) { return false; } if (!noe.emitAssignment()) { return false; } if (!bce->emit1(JSOP_POP)) { return false; } return true; }; // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasLocalBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) { return false; } } // 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 (funbox->hasThisBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis, JSOP_FUNCTIONTHIS)) { return false; } } return true; } bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) { FunctionBox* funbox = sc->asFunctionBox(); if (!emitTree(funBody)) { return false; } if (funbox->needsFinalYield()) { // If we fall off the end of a generator, do a final yield. bool needsIteratorResult = funbox->needsIteratorResult(); if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { return false; } } if (!emit1(JSOP_UNDEFINED)) { return false; } if (needsIteratorResult) { if (!emitFinishIteratorResult(true)) { return false; } } if (!emit1(JSOP_SETRVAL)) { return false; } if (!emitGetDotGeneratorInInnermostScope()) { 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 (funbox->isDerivedClassConstructor()) { if (!emitCheckDerivedClassConstructorReturn()) { return false; } } return true; } bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { NameOpEmitter noe(this, name->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } // The caller has pushed the RHS to the top of the stack. Assert that the // name is lexical and no BIND[G]NAME ops were emitted. MOZ_ASSERT(noe.loc().isLexical()); MOZ_ASSERT(!noe.emittedBindOp()); if (!noe.emitAssignment()) { return false; } return true; } // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 // (BindingClassDeclarationEvaluation). bool BytecodeEmitter::emitClass(ClassNode* classNode) { ClassNames* names = classNode->names(); ParseNode* heritageExpression = classNode->heritage(); ListNode* classMembers = classNode->memberList(); CodeNode* constructor = nullptr; for (ParseNode* mn : classMembers->contents()) { if (mn->is()) { // TODO(khyperia): Implement private field access. return false; } ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(ParseNodeKind::ObjectPropertyName) || methodName.isKind(ParseNodeKind::String)) && methodName.as().atom() == cx->names().constructor) { constructor = &method.method(); break; } } bool savedStrictness = sc->setLocalStrictMode(true); Maybe tdzCache; Maybe emitterScope; if (names) { tdzCache.emplace(this); emitterScope.emplace(this); if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode->scopeBindings())) { return false; } } // Pseudocode for class declarations: // // class extends BaseExpression { // constructor() { ... } // ... // } // // // if defined { // let heritage = BaseExpression; // // if (heritage !== null) { // funProto = heritage; // objProto = heritage.prototype; // } else { // funProto = %FunctionPrototype%; // objProto = null; // } // } else { // objProto = %ObjectPrototype%; // } // // let homeObject = ObjectCreate(objProto); // // if defined { // if defined { // cons = DefineMethod(, proto=homeObject, funProto=funProto); // } else { // cons = DefineMethod(, proto=homeObject); // } // } else { // if defined { // cons = DefaultDerivedConstructor(proto=homeObject, funProto=funProto); // } else { // cons = DefaultConstructor(proto=homeObject); // } // } // // cons.prototype = homeObject; // homeObject.constructor = cons; // // EmitPropertyList(...) // 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) { InternalIfEmitter ifThenElse(this); if (!emitTree(heritageExpression)) { // ... HERITAGE return false; } // Heritage must be null or a non-generator constructor if (!emit1(JSOP_CHECKCLASSHERITAGE)) { // ... HERITAGE return false; } // [IF] (heritage !== null) if (!emit1(JSOP_DUP)) { // ... HERITAGE HERITAGE return false; } if (!emit1(JSOP_NULL)) { // ... HERITAGE HERITAGE NULL return false; } if (!emit1(JSOP_STRICTNE)) { // ... HERITAGE NE return false; } // [THEN] funProto = heritage, objProto = heritage.prototype if (!ifThenElse.emitThenElse()) { return false; } if (!emit1(JSOP_DUP)) { // ... HERITAGE HERITAGE return false; } if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) { // ... HERITAGE PROTO return false; } // [ELSE] funProto = %FunctionPrototype%, objProto = null if (!ifThenElse.emitElse()) { return false; } if (!emit1(JSOP_POP)) { // ... return false; } if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) { // ... PROTO return false; } if (!emit1(JSOP_NULL)) { // ... PROTO NULL return false; } // [ENDIF] if (!ifThenElse.emitEnd()) { return false; } if (!emit1(JSOP_OBJWITHPROTO)) { // ... HERITAGE HOMEOBJ return false; } if (!emit1(JSOP_SWAP)) { // ... HOMEOBJ HERITAGE return false; } } else { if (!emitNewInit()) { // ... HOMEOBJ return false; } } // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE // is not used, an implicit value of %FunctionPrototype% is implied. if (constructor) { if (!emitFunction(constructor, !!heritageExpression)) { // ... HOMEOBJ CONSTRUCTOR return false; } if (constructor->funbox()->needsHomeObject()) { if (!emit2(JSOP_INITHOMEOBJECT, 0)) { // ... HOMEOBJ CONSTRUCTOR return false; } } } else { // In the case of default class constructors, emit the start and end // offsets in the source buffer as source notes so that when we // actually make the constructor during execution, we can give it the // correct toString output. ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin); ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end); if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) { return false; } JSAtom *name = names ? names->innerBinding()->as().atom() : cx->names().empty; if (heritageExpression) { if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) { // ... HOMEOBJ CONSTRUCTOR return false; } } else { if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) { // ... HOMEOBJ CONSTRUCTOR return false; } } } if (!emit1(JSOP_SWAP)) { // ... CONSTRUCTOR HOMEOBJ return false; } if (!emit1(JSOP_DUP2)) { // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ return false; } if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) { // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR return false; } if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) { // ... CONSTRUCTOR HOMEOBJ return false; } RootedPlainObject obj(cx); if (!emitPropertyList(classMembers, &obj, ClassBody)) { // ... CONSTRUCTOR HOMEOBJ return false; } if (!emit1(JSOP_POP)) { // ... CONSTRUCTOR return false; } if (names) { NameNode* innerName = names->innerBinding(); if (!emitLexicalInitialization(innerName)) { // ... CONSTRUCTOR return false; } // Pop the inner scope. if (!emitterScope->leave(this)) { return false; } emitterScope.reset(); if (NameNode* outerName = names->outerBinding()) { if (!emitLexicalInitialization(outerName)) { // ... CONSTRUCTOR return false; } // Only class statements make outer bindings, and they do not leave // themselves on the stack. if (!emit1(JSOP_POP)) { // ... return false; } } } // The CONSTRUCTOR is left on stack if this is an expression. MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); return true; } bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) { MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefault)); ParseNode* nameNode = exportNode->left(); if (!emitTree(nameNode)) { return false; } if (ParseNode* binding = exportNode->right()) { if (!emitLexicalInitialization(&binding->as())) { return false; } if (nameNode->isDirectRHSAnonFunction()) { HandlePropertyName name = cx->names().default_; if (!setOrEmitSetFunName(nameNode, name)) { return false; } } if (!emit1(JSOP_POP)) { return false; } } return true; } bool BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { if (!CheckRecursionLimit(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 && !ParseNodeRequiresSpecialLineNumberNotes(pn)) { if (!updateLineNumberNotes(pn->pn_pos.begin)) { return false; } } switch (pn->getKind()) { case ParseNodeKind::Function: if (!emitFunction(&pn->as())) { return false; } break; case ParseNodeKind::ParamsBody: if (!emitFunctionFormalParametersAndBody(&pn->as())) { return false; } break; case ParseNodeKind::If: if (!emitIf(&pn->as())) { return false; } break; case ParseNodeKind::Switch: if (!emitSwitch(&pn->as())) { return false; } break; case ParseNodeKind::While: if (!emitWhile(&pn->as())) { return false; } break; case ParseNodeKind::DoWhile: if (!emitDo(&pn->as())) { return false; } break; case ParseNodeKind::For: if (!emitFor(&pn->as())) { return false; } break; case ParseNodeKind::Break: // Ensure that the column of the 'break' is set properly. if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!emitBreak(pn->as().label())) { return false; } break; case ParseNodeKind::Continue: // Ensure that the column of the 'continue' is set properly. if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!emitContinue(pn->as().label())) { return false; } break; case ParseNodeKind::With: if (!emitWith(&pn->as())) { return false; } break; case ParseNodeKind::Try: if (!emitTry(&pn->as())) { return false; } break; case ParseNodeKind::Catch: if (!emitCatch(&pn->as())) { return false; } break; case ParseNodeKind::Var: if (!emitDeclarationList(&pn->as())) { return false; } break; case ParseNodeKind::Return: if (!emitReturn(&pn->as())) { return false; } break; case ParseNodeKind::YieldStar: if (!emitYieldStar(pn->as().kid())) { return false; } break; case ParseNodeKind::Generator: if (!emit1(JSOP_GENERATOR)) { return false; } break; case ParseNodeKind::InitialYield: if (!emitInitialYield(&pn->as())) { return false; } break; case ParseNodeKind::Yield: if (!emitYield(&pn->as())) { return false; } break; case ParseNodeKind::Await: if (!emitAwaitInInnermostScope(&pn->as())) { return false; } break; case ParseNodeKind::StatementList: if (!emitStatementList(&pn->as())) { return false; } break; case ParseNodeKind::EmptyStatement: break; case ParseNodeKind::ExpressionStatement: if (!emitExpressionStatement(&pn->as())) { return false; } break; case ParseNodeKind::Label: if (!emitLabeledStatement(&pn->as())) { return false; } break; case ParseNodeKind::Comma: if (!emitSequenceExpr(&pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::Assign: case ParseNodeKind::AddAssign: case ParseNodeKind::SubAssign: case ParseNodeKind::BitOrAssign: case ParseNodeKind::BitXorAssign: case ParseNodeKind::BitAndAssign: case ParseNodeKind::LshAssign: case ParseNodeKind::RshAssign: case ParseNodeKind::UrshAssign: case ParseNodeKind::MulAssign: case ParseNodeKind::DivAssign: case ParseNodeKind::ModAssign: case ParseNodeKind::PowAssign: { AssignmentNode* assignNode = &pn->as(); if (!emitAssignment(assignNode->left(), CompoundAssignmentParseNodeKindToJSOp(assignNode->getKind()), assignNode->right())) { return false; } break; } case ParseNodeKind::Conditional: if (!emitConditionalExpression(pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::Or: case ParseNodeKind::And: if (!emitLogical(&pn->as())) { return false; } break; case ParseNodeKind::Add: case ParseNodeKind::Sub: case ParseNodeKind::BitOr: case ParseNodeKind::BitXor: case ParseNodeKind::BitAnd: case ParseNodeKind::StrictEq: case ParseNodeKind::Eq: case ParseNodeKind::StrictNe: case ParseNodeKind::Ne: case ParseNodeKind::Lt: case ParseNodeKind::Le: case ParseNodeKind::Gt: case ParseNodeKind::Ge: case ParseNodeKind::In: case ParseNodeKind::InstanceOf: case ParseNodeKind::Lsh: case ParseNodeKind::Rsh: case ParseNodeKind::Ursh: case ParseNodeKind::Star: case ParseNodeKind::Div: case ParseNodeKind::Mod: if (!emitLeftAssociative(&pn->as())) { return false; } break; case ParseNodeKind::Pow: if (!emitRightAssociative(&pn->as())) { return false; } break; case ParseNodeKind::Pipeline: if (!emitPipeline(&pn->as())) { return false; } break; case ParseNodeKind::TypeOfName: if (!emitTypeof(&pn->as(), JSOP_TYPEOF)) { return false; } break; case ParseNodeKind::TypeOfExpr: if (!emitTypeof(&pn->as(), JSOP_TYPEOFEXPR)) { return false; } break; case ParseNodeKind::Throw: case ParseNodeKind::Void: case ParseNodeKind::Not: case ParseNodeKind::BitNot: case ParseNodeKind::Pos: case ParseNodeKind::Neg: if (!emitUnary(&pn->as())) { return false; } break; case ParseNodeKind::PreIncrement: case ParseNodeKind::PreDecrement: case ParseNodeKind::PostIncrement: case ParseNodeKind::PostDecrement: if (!emitIncOrDec(&pn->as())) { return false; } break; case ParseNodeKind::DeleteName: if (!emitDeleteName(&pn->as())) { return false; } break; case ParseNodeKind::DeleteProp: if (!emitDeleteProperty(&pn->as())) { return false; } break; case ParseNodeKind::DeleteElem: if (!emitDeleteElement(&pn->as())) { return false; } break; case ParseNodeKind::DeleteExpr: if (!emitDeleteExpression(&pn->as())) { return false; } break; case ParseNodeKind::Dot: { PropertyAccess* prop = &pn->as(); // TODO(khyperia): Implement private field access. bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::Get, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS return false; } } else { if (!emitPropLHS(prop)) { // OBJ return false; } } if (!poe.emitGet(prop->key().atom())) { // PROP return false; } break; } case ParseNodeKind::Elem: { PropertyByValue* elem = &pn->as(); bool isSuper = elem->isSuper(); ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super] // // THIS KEY // // [Other] // // OBJ KEY return false; } if (!eoe.emitGet()) { // ELEM return false; } break; } case ParseNodeKind::New: case ParseNodeKind::TaggedTemplate: case ParseNodeKind::Call: case ParseNodeKind::SuperCall: if (!emitCallOrNew(&pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::LexicalScope: if (!emitLexicalScope(&pn->as())) { return false; } break; case ParseNodeKind::Const: case ParseNodeKind::Let: if (!emitDeclarationList(&pn->as())) { return false; } break; case ParseNodeKind::Import: MOZ_ASSERT(sc->isModuleContext()); break; case ParseNodeKind::Export: { MOZ_ASSERT(sc->isModuleContext()); UnaryNode* node = &pn->as(); ParseNode* decl = node->kid(); if (decl->getKind() != ParseNodeKind::ExportSpecList) { if (!emitTree(decl)) { return false; } } break; } case ParseNodeKind::ExportDefault: MOZ_ASSERT(sc->isModuleContext()); if (!emitExportDefault(&pn->as())) { return false; } break; case ParseNodeKind::ExportFrom: MOZ_ASSERT(sc->isModuleContext()); break; case ParseNodeKind::CallSiteObj: if (!emitCallSiteObject(&pn->as())) { return false; } break; case ParseNodeKind::Array: if (!emitArrayLiteral(&pn->as())) { return false; } break; case ParseNodeKind::Object: if (!emitObject(&pn->as())) { return false; } break; case ParseNodeKind::Name: if (!emitGetName(&pn->as())) { return false; } break; case ParseNodeKind::TemplateStringList: if (!emitTemplateString(&pn->as())) { return false; } break; case ParseNodeKind::TemplateString: case ParseNodeKind::String: if (!emitAtomOp(pn->as().atom(), JSOP_STRING)) { return false; } break; case ParseNodeKind::Number: if (!emitNumberOp(pn->as().value())) { return false; } break; case ParseNodeKind::RegExp: if (!emitRegExp(objectList.add(pn->as().objbox()))) { return false; } break; case ParseNodeKind::True: case ParseNodeKind::False: case ParseNodeKind::Null: case ParseNodeKind::RawUndefined: if (!emit1(pn->getOp())) { return false; } break; case ParseNodeKind::This: if (!emitThisLiteral(&pn->as())) { return false; } break; case ParseNodeKind::Debugger: if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!emit1(JSOP_DEBUGGER)) { return false; } break; case ParseNodeKind::Class: if (!emitClass(&pn->as())) { return false; } break; case ParseNodeKind::NewTarget: if (!emit1(JSOP_NEWTARGET)) { return false; } break; case ParseNodeKind::ImportMeta: if (!emit1(JSOP_IMPORTMETA)) { return false; } break; case ParseNodeKind::CallImport: if (!emitTree(pn->as().right()) || !emit1(JSOP_DYNAMIC_IMPORT)) { return false; } break; case ParseNodeKind::SetThis: if (!emitSetThis(&pn->as())) { return false; } break; case ParseNodeKind::PropertyName: case ParseNodeKind::PosHolder: MOZ_FALLTHROUGH_ASSERT("Should never try to emit ParseNodeKind::PosHolder or ::Property"); 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(JSContext* cx, SrcNotesVector& notes, unsigned* index) { if (!notes.growBy(1)) { ReportOutOfMemory(cx); return false; } *index = notes.length() - 1; return true; } bool BytecodeEmitter::addTryNote(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end) { // The tryNoteList stores offsets relative to current section should must // be main section. During tryNoteList.finish(), the prologueLength will be // added to correct offset. MOZ_ASSERT(!inPrologue()); return tryNoteList.append(kind, stackDepth, start, end); } 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)) { reportError(nullptr, JSMSG_NEED_DIET, js_script_str); 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); MOZ_ASSERT(prologue.notes.length() == 0); MOZ_ASSERT(prologue.lastNoteOffset == 0); // 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. ptrdiff_t offset = prologueOffset(); 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 + 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 = main.notes.length() + 1; return true; } void BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) { MOZ_ASSERT(prologue.notes.length() == 0); unsigned mainCount = main.notes.length(); // nsrcnotes includes SN_MAKE_TERMINATOR in addition to main.notes. MOZ_ASSERT(mainCount == nsrcnotes - 1); PodCopy(destination, main.notes.begin(), mainCount); SN_MAKE_TERMINATOR(&destination[mainCount]); } void CGNumberList::finish(mozilla::Span array) { MOZ_ASSERT(length() == array.size()); for (unsigned i = 0; i < length(); i++) { array[i].init(DoubleValue(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 indexed 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. */ unsigned CGObjectList::add(ObjectBox* objbox) { MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; return length++; } void CGObjectList::finish(mozilla::Span array) { MOZ_ASSERT(length <= INDEX_LIMIT); MOZ_ASSERT(length == array.size()); ObjectBox* objbox = lastbox; for (GCPtrObject& obj : mozilla::Reversed(array)) { MOZ_ASSERT(obj == nullptr); MOZ_ASSERT(objbox->object->isTenured()); if (objbox->isFunctionBox()) { objbox->asFunctionBox()->finish(); } obj.init(objbox->object); objbox = objbox->emitLink; } } void CGScopeList::finish(mozilla::Span array) { MOZ_ASSERT(length() <= INDEX_LIMIT); MOZ_ASSERT(length() == array.size()); for (uint32_t i = 0; i < length(); i++) { array[i].init(vector[i]); } } 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); // Offsets are given relative to sections, but we only expect main-section // to have TryNotes. In finish() we will fixup base offset. 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(mozilla::Span array, uint32_t prologueLength) { MOZ_ASSERT(length() == array.size()); for (unsigned i = 0; i < length(); i++) { list[i].start += prologueLength; array[i] = list[i]; } } bool CGScopeNoteList::append(uint32_t scopeIndex, uint32_t offset, bool inPrologue, uint32_t parent) { CGScopeNote note; mozilla::PodZero(¬e); // Offsets are given relative to sections. In finish() we will fixup base // offset if needed. note.index = scopeIndex; note.start = offset; note.parent = parent; note.startInPrologue = inPrologue; return list.append(note); } void CGScopeNoteList::recordEnd(uint32_t index, uint32_t offset, bool inPrologue) { MOZ_ASSERT(index < length()); MOZ_ASSERT(list[index].length == 0); list[index].end = offset; list[index].endInPrologue = inPrologue; } void CGScopeNoteList::finish(mozilla::Span array, uint32_t prologueLength) { MOZ_ASSERT(length() == array.size()); for (unsigned i = 0; i < length(); i++) { if (!list[i].startInPrologue) { list[i].start += prologueLength; } if (!list[i].endInPrologue && list[i].end != UINT32_MAX) { list[i].end += prologueLength; } MOZ_ASSERT(list[i].end >= list[i].start); list[i].length = list[i].end - list[i].start; array[i] = list[i]; } } void CGResumeOffsetList::finish(mozilla::Span array, uint32_t prologueLength) { MOZ_ASSERT(length() == array.size()); for (unsigned i = 0; i < length(); i++) { array[i] = prologueLength + list[i]; } } 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; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -1,860 +1,862 @@ /* -*- 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. */ #ifndef frontend_BytecodeEmitter_h #define frontend_BytecodeEmitter_h #include "mozilla/Attributes.h" #include "mozilla/Span.h" #include "ds/InlineTable.h" #include "frontend/BCEParserHandle.h" #include "frontend/EitherParser.h" #include "frontend/JumpList.h" #include "frontend/NameFunctions.h" #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" #include "frontend/SourceNotes.h" #include "frontend/ValueUsage.h" #include "vm/BytecodeUtil.h" #include "vm/Interpreter.h" #include "vm/Iteration.h" namespace js { namespace frontend { class CGNumberList { Vector list; public: explicit CGNumberList(JSContext* cx) : list(cx) {} MOZ_MUST_USE bool append(double v) { return list.append(v); } size_t length() const { return list.length(); } void finish(mozilla::Span array); }; struct CGObjectList { uint32_t length; /* number of emitted so far objects */ ObjectBox* lastbox; /* last emitted object */ CGObjectList() : length(0), lastbox(nullptr) {} unsigned add(ObjectBox* objbox); void finish(mozilla::Span array); }; struct MOZ_STACK_CLASS CGScopeList { Rooted> vector; explicit CGScopeList(JSContext* cx) : vector(cx, GCVector(cx)) { } bool append(Scope* scope) { return vector.append(scope); } uint32_t length() const { return vector.length(); } void finish(mozilla::Span array); }; struct CGTryNoteList { Vector list; explicit CGTryNoteList(JSContext* cx) : list(cx) {} // Start/end offset are relative to main section and will be patch in // finish(). MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end); size_t length() const { return list.length(); } void finish(mozilla::Span array, uint32_t prologueLength); }; struct CGScopeNote : public ScopeNote { // The end offset. Used to compute the length; may need adjusting first if // in the prologue. uint32_t end; // Is the start offset in the prologue? bool startInPrologue; // Is the end offset in the prologue? bool endInPrologue; }; struct CGScopeNoteList { Vector list; explicit CGScopeNoteList(JSContext* cx) : list(cx) {} MOZ_MUST_USE bool append(uint32_t scopeIndex, uint32_t offset, bool inPrologue, uint32_t parent); void recordEnd(uint32_t index, uint32_t offset, bool inPrologue); size_t length() const { return list.length(); } void finish(mozilla::Span array, uint32_t prologueLength); }; struct CGResumeOffsetList { Vector list; uint32_t numYields; uint32_t numAwaits; explicit CGResumeOffsetList(JSContext* cx) : list(cx), numYields(0), numAwaits(0) {} MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } void finish(mozilla::Span array, uint32_t prologueLength); }; // Have a few inline elements, so as to avoid heap allocation for tiny // sequences. See bug 1390526. typedef Vector BytecodeVector; typedef Vector SrcNotesVector; class CallOrNewEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; class TDZCheckCache; struct MOZ_STACK_CLASS BytecodeEmitter { SharedContext* const sc; /* context shared between parsing and bytecode generation */ JSContext* const cx; BytecodeEmitter* const parent; /* enclosing function or global context */ Rooted script; /* the JSScript we're ultimately producing */ Rooted lazyScript; /* the lazy script if mode is LazyFunction, nullptr otherwise. */ struct EmitSection { BytecodeVector code; /* bytecode */ SrcNotesVector notes; /* source notes, see below */ ptrdiff_t lastNoteOffset; /* code offset for last source note */ // Line number for srcnotes. // // WARNING: If this becomes out of sync with already-emitted srcnotes, // we can get undefined behavior. uint32_t currentLine; // Zero-based column index on currentLine of last SRC_COLSPAN-annotated // opcode. // // WARNING: If this becomes out of sync with already-emitted srcnotes, // we can get undefined behavior. uint32_t lastColumn; JumpTarget lastTarget; // Last jump target emitted. EmitSection(JSContext* cx, uint32_t lineNum) : code(cx), notes(cx), lastNoteOffset(0), currentLine(lineNum), lastColumn(0), lastTarget{ -1 - ptrdiff_t(JSOP_JUMPTARGET_LENGTH) } {} }; EmitSection prologue, main, *current; // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be initialized. // Use |parser| instead. mozilla::Maybe ep_; BCEParserHandle *parser; PooledMapPtr atomIndices; /* literals indexed for mapping */ unsigned firstLine; /* first line, for JSScript::initFromEmitter */ uint32_t maxFixedSlots; /* maximum number of fixed frame slots so far */ uint32_t maxStackDepth; /* maximum number of expression stack slots so far */ int32_t stackDepth; /* current stack depth in script frame */ unsigned emitLevel; /* emitTree recursion level */ uint32_t bodyScopeIndex; /* index into scopeList of the body scope */ EmitterScope* varEmitterScope; NestableControl* innermostNestableControl; EmitterScope* innermostEmitterScope_; TDZCheckCache* innermostTDZCheckCache; #ifdef DEBUG bool unstableEmitterScope; friend class AutoCheckUnstableEmitterScope; #endif EmitterScope* innermostEmitterScope() const { MOZ_ASSERT(!unstableEmitterScope); return innermostEmitterScopeNoCheck(); } EmitterScope* innermostEmitterScopeNoCheck() const { return innermostEmitterScope_; } CGNumberList numberList; /* number constants to be included with the script */ CGObjectList objectList; /* list of emitted objects */ CGScopeList scopeList; /* list of emitted scopes */ CGTryNoteList tryNoteList; /* list of emitted try notes */ CGScopeNoteList scopeNoteList; /* list of emitted block scope notes */ // Certain ops (yield, await, gosub) have an entry in the script's // resumeOffsets list. This can be used to map from the op's resumeIndex to // the bytecode offset of the next pc. This indirection makes it easy to // resume in the JIT (because BaselineScript stores a resumeIndex => native // code array). CGResumeOffsetList resumeOffsetList; uint16_t typesetCount; /* Number of JOF_TYPESET opcodes generated */ bool hasSingletons:1; /* script contains singleton initializer JSOP_OBJECT */ bool hasTryFinally:1; /* script contains finally block */ bool emittingRunOnceLambda:1; /* true while emitting a lambda which is only expected to run once. */ bool isRunOnceLambda(); enum EmitterMode { Normal, /* * Emit JSOP_GETINTRINSIC instead of JSOP_GETNAME and assert that * JSOP_GETNAME and JSOP_*GNAME don't ever get emitted. See the comment * for the field |selfHostingMode| in Parser.h for details. */ SelfHosting, /* * Check the static scope chain of the root function for resolving free * variable accesses in the script. */ LazyFunction }; const EmitterMode emitterMode; MOZ_INIT_OUTSIDE_CTOR uint32_t scriptStartOffset; bool scriptStartOffsetSet; // The end location of a function body that is being emitted. MOZ_INIT_OUTSIDE_CTOR uint32_t functionBodyEndPos; // Whether functionBodyEndPos was set. bool functionBodyEndPosSet; /* * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" * space above their tempMark points. This means that you cannot alloc from * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter * destruction. */ private: // Internal constructor, for delegation use only. BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode); void initFromBodyPosition(TokenPos bodyPosition); public: BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode = Normal); BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode = Normal); template BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode = Normal) : BytecodeEmitter(parent, EitherParser(parser), sc, script, lazyScript, lineNum, emitterMode) {} // An alternate constructor that uses a TokenPos for the starting // line and that sets functionBodyEndPos as well. BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* parser, SharedContext* sc, HandleScript script, Handle lazyScript, TokenPos bodyPosition, EmitterMode emitterMode = Normal) : BytecodeEmitter(parent, parser, sc, script, lazyScript, parser->errorReporter().lineAt(bodyPosition.begin), emitterMode) { initFromBodyPosition(bodyPosition); } BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, SharedContext* sc, HandleScript script, Handle lazyScript, TokenPos bodyPosition, EmitterMode emitterMode = Normal) : BytecodeEmitter(parent, parser, sc, script, lazyScript, parser.errorReporter().lineAt(bodyPosition.begin), emitterMode) { initFromBodyPosition(bodyPosition); } template BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, TokenPos bodyPosition, EmitterMode emitterMode = Normal) : BytecodeEmitter(parent, EitherParser(parser), sc, script, lazyScript, bodyPosition, emitterMode) {} MOZ_MUST_USE bool init(); template T* findInnermostNestableControl() const; template bool */> T* findInnermostNestableControl(Predicate predicate) const; NameLocation lookupName(JSAtom* name); // To implement Annex B and the formal parameter defaults scope semantics // requires accessing names that would otherwise be shadowed. This method // returns the access location of a name that is known to be bound in a // target scope. mozilla::Maybe locationOfNameBoundInScope(JSAtom* name, EmitterScope* target); // Get the location of a name known to be bound in the function scope, // starting at the source scope. mozilla::Maybe locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source); mozilla::Maybe locationOfNameBoundInFunctionScope(JSAtom* name) { return locationOfNameBoundInFunctionScope(name, innermostEmitterScope()); } void setVarEmitterScope(EmitterScope* emitterScope) { MOZ_ASSERT(emitterScope); MOZ_ASSERT(!varEmitterScope); varEmitterScope = emitterScope; } Scope* outermostScope() const { return scopeList.vector[0]; } Scope* innermostScope() const; MOZ_ALWAYS_INLINE MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, uint32_t* indexp) { MOZ_ASSERT(atomIndices); AtomIndexMap::AddPtr p = atomIndices->lookupForAdd(atom); if (p) { *indexp = p->value(); return true; } uint32_t index = atomIndices->count(); if (!atomIndices->add(p, atom, index)) { ReportOutOfMemory(cx); return false; } *indexp = index; return true; } bool isInLoop(); MOZ_MUST_USE bool checkSingletonContext(); // Check whether our function is in a run-once context (a toplevel // run-one script or a run-once lambda). MOZ_MUST_USE bool checkRunOnceContext(); bool needsImplicitThis(); void tellDebuggerAboutCompiledScript(JSContext* cx); BytecodeVector& code() const { return current->code; } jsbytecode* code(ptrdiff_t offset) const { return current->code.begin() + offset; } ptrdiff_t offset() const { return current->code.end() - current->code.begin(); } ptrdiff_t prologueOffset() const { return prologue.code.end() - prologue.code.begin(); } void switchToMain() { current = &main; } void switchToPrologue() { current = &prologue; } bool inPrologue() const { return current == &prologue; } SrcNotesVector& notes() const { // Prologue shouldn't have source notes. MOZ_ASSERT(!inPrologue()); return current->notes; } ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; } unsigned currentLine() const { return current->currentLine; } // Check if the last emitted opcode is a jump target. bool lastOpcodeIsJumpTarget() const { return offset() - current->lastTarget.offset == ptrdiff_t(JSOP_JUMPTARGET_LENGTH); } // JumpTarget should not be part of the emitted statement, as they can be // aliased by multiple statements. If we included the jump target as part of // the statement we might have issues where the enclosing statement might // not contain all the opcodes of the enclosed statements. ptrdiff_t lastNonJumpTargetOffset() const { return lastOpcodeIsJumpTarget() ? current->lastTarget.offset : offset(); } void setFunctionBodyEndPos(TokenPos pos) { functionBodyEndPos = pos.end; functionBodyEndPosSet = true; } void setScriptStartOffsetIfUnset(TokenPos pos) { if (!scriptStartOffsetSet) { scriptStartOffset = pos.begin; scriptStartOffsetSet = true; } } void reportError(ParseNode* pn, unsigned errorNumber, ...); void reportError(const mozilla::Maybe& maybeOffset, unsigned errorNumber, ...); bool reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...); bool reportExtraWarning(const mozilla::Maybe& maybeOffset, unsigned errorNumber, ...); // If pn contains a useful expression, return true with *answer set to true. // If pn contains a useless expression, return true with *answer set to // false. Return false on error. // // The caller should initialize *answer to false and invoke this function on // an expression statement or similar subtree to decide whether the tree // could produce code that has any side effects. For an expression // statement, we define useless code as code with no side effects, because // the main effect, the value left on the stack after the code executes, // will be discarded by a pop bytecode. MOZ_MUST_USE bool checkSideEffects(ParseNode* pn, bool* answer); #ifdef DEBUG MOZ_MUST_USE bool checkStrictOrSloppy(JSOp op); #endif // Add TryNote to the tryNoteList array. The start and end offset are // relative to current section. MOZ_MUST_USE bool addTryNote(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end); // Append a new source note of the given type (and therefore size) to the // notes dynamic array, updating noteCount. Return the new note's index // within the array pointed at by current->notes as outparam. MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp = nullptr); MOZ_MUST_USE bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, unsigned* indexp = nullptr); void copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes); MOZ_MUST_USE bool setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset); // NB: this function can add at most one extra extended delta note. MOZ_MUST_USE bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta); // Finish taking source notes in cx's notePool. If successful, the final // source note count is stored in the out outparam. MOZ_MUST_USE bool finishTakingSrcNotes(uint32_t* out); // Control whether emitTree emits a line number note. enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE }; // Emit code for the tree rooted at pn. MOZ_MUST_USE bool emitTree(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); // Emit global, eval, or module code for tree rooted at body. Always // encompasses the entire source. MOZ_MUST_USE bool emitScript(ParseNode* body); // Emit function code for the tree rooted at body. enum class TopLevelFunction { No, Yes }; MOZ_MUST_USE bool emitFunctionScript(CodeNode* funNode, TopLevelFunction isTopLevel); // If op is JOF_TYPESET (see the type barriers comment in TypeInference.h), // reserve a type set to store its result. void checkTypeSet(JSOp op); void updateDepth(ptrdiff_t target); MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset); MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset); JSOp strictifySetNameOp(JSOp op); MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset); // Emit one bytecode. MOZ_MUST_USE bool emit1(JSOp op); // Emit two bytecodes, an opcode (op) with a byte of immediate operand // (op1). MOZ_MUST_USE bool emit2(JSOp op, uint8_t op1); // Emit three bytecodes, an opcode with two bytes of immediate operands. MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); // Helper to emit JSOP_DUPAT. The argument is the value's depth on the // JS stack, as measured from the top. MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop); // Helper to emit JSOP_POP or JSOP_POPN. MOZ_MUST_USE bool emitPopN(unsigned n); // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); // Helper to emit JSOP_CHECKISCALLABLE. MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind); // Push whether the value atop of the stack is non-undefined and non-null. MOZ_MUST_USE bool emitPushNotUndefinedOrNull(); // Emit a bytecode followed by an uint16 immediate operand stored in // big-endian order. MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); // Emit a bytecode followed by an uint32 immediate operand. MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand); // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr); MOZ_MUST_USE bool emitNumberOp(double dval); MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn); MOZ_MUST_USE bool emitGetFunctionThis(NameNode* thisName); MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe& offset); MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase); MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode); MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn(); // Handle jump opcodes and jump targets. MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target); MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump); MOZ_MUST_USE bool emitJump(JSOp op, JumpList* jump); MOZ_MUST_USE bool emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump, JumpTarget* fallthrough); void patchJumpsToTarget(JumpList jump, JumpTarget target); MOZ_MUST_USE bool emitJumpTargetAndPatch(JumpList jump); MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, const mozilla::Maybe& sourceCoordOffset); MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); MOZ_MUST_USE bool emitCallIncDec(UnaryNode* incDec); mozilla::Maybe getOffsetForLoop(ParseNode* nextpn); MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType = SRC_NULL); MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index); MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index); MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op); MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op); MOZ_MUST_USE bool emitArrayLiteral(ListNode* array); MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count); MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op); MOZ_MUST_USE bool emitInternedObjectOp(uint32_t index, JSOp op); MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op); MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op); MOZ_MUST_USE bool emitRegExp(uint32_t index); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(CodeNode* funNode, bool needsProto = false); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode); MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset); MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should // instead be emitted using EmitVarOp. In special cases, when the caller // definitely knows that a given local slot is unaliased, this function may be // used as a non-asserting version of emitUint16Operand. MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot); MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot); MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc); MOZ_MUST_USE bool emitGetName(JSAtom* name) { return emitGetNameAtLocation(name, lookupName(name)); } MOZ_MUST_USE bool emitGetName(NameNode* name); MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc); MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec); MOZ_MUST_USE bool emitDeclarationList(ListNode* declList); MOZ_MUST_USE bool emitSingleDeclaration(ListNode* declList, NameNode* decl, ParseNode* initializer); MOZ_MUST_USE bool emitNewInit(); MOZ_MUST_USE bool emitSingletonInitialiser(ListNode* objOrArray); MOZ_MUST_USE bool emitPrepareIteratorResult(); MOZ_MUST_USE bool emitFinishIteratorResult(bool done); MOZ_MUST_USE bool iteratorResultShape(unsigned* shape); MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() { return emitGetDotGeneratorInScope(*innermostEmitterScope()); } MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope); MOZ_MUST_USE bool allocateResumeIndexForCurrentOffset(uint32_t* resumeIndex); MOZ_MUST_USE bool emitInitialYield(UnaryNode* yieldNode); MOZ_MUST_USE bool emitYield(UnaryNode* yieldNode); MOZ_MUST_USE bool emitYieldOp(JSOp op); MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); MOZ_MUST_USE bool emitAwaitInInnermostScope() { return emitAwaitInScope(*innermostEmitterScope()); } MOZ_MUST_USE bool emitAwaitInInnermostScope(UnaryNode* awaitNode); MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope); MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop); MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec); MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow); MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, bool isGenerator); MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName); // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, EmitElemOption opts); MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe); MOZ_MUST_USE bool emitElemOpBase(JSOp op); MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op); MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec); MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause); MOZ_MUST_USE bool emitIf(TernaryNode* ifNode); MOZ_MUST_USE bool emitWith(BinaryNode* withNode); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement(const LabeledStatement* pn); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope(LexicalScopeNode* lexicalScope); MOZ_MUST_USE bool emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); + MOZ_MUST_USE bool emitGoSub(JumpList* jump); + enum DestructuringFlavor { // Destructuring into a declaration. DestructuringDeclaration, // Destructuring into a formal parameter, when the formal parameters // contain an expression that might be evaluated, and thus require // this destructuring to assign not into the innermost scope that // contains the function body's vars, but into its enclosing scope for // parameter expressions. DestructuringFormalParameterInVarScope, // Destructuring as part of an AssignmentExpression. DestructuringAssignment }; // emitDestructuringLHSRef emits the lhs expression's reference. // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. // If there's nothing to evaluate for the reference, it emits nothing. // |emitted| parameter receives the number of values pushed onto the stack. MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); // emitSetOrInitializeDestructuring assumes the lhs expression's reference // and the to-be-destructured value has been pushed on the stack. It emits // code to destructure a single lhs expression (either a name or a compound // []/{} expression). MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); // emitDestructuringObjRestExclusionSet emits the property exclusion set // for the rest-property in an object pattern. MOZ_MUST_USE bool emitDestructuringObjRestExclusionSet(ListNode* pattern); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or // {} lhs expression. MOZ_MUST_USE bool emitDestructuringOps(ListNode* pattern, DestructuringFlavor flav); MOZ_MUST_USE bool emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav); MOZ_MUST_USE bool emitDestructuringOpsObject(ListNode* pattern, DestructuringFlavor flav); enum class CopyOption { Filtered, Unfiltered }; // Calls either the |CopyDataProperties| or the // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or // two in the latter case) elements from the stack. MOZ_MUST_USE bool emitCopyDataProperties(CopyOption option); // emitIterator expects the iterable to already be on the stack. // It will replace that stack value with the corresponding iterator MOZ_MUST_USE bool emitIterator(); MOZ_MUST_USE bool emitAsyncIterator(); // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(const mozilla::Maybe& callSourceCoordOffset, IteratorKind kind = IteratorKind::Sync, bool allowSelfHosted = false); MOZ_MUST_USE bool emitIteratorCloseInScope(EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync, CompletionKind completionKind = CompletionKind::Normal, bool allowSelfHosted = false); MOZ_MUST_USE bool emitIteratorCloseInInnermostScope(IteratorKind iterKind = IteratorKind::Sync, CompletionKind completionKind = CompletionKind::Normal, bool allowSelfHosted = false) { return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, completionKind, allowSelfHosted); } template MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. // |pattern| is a lhs node of the default expression. If it's an // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| // is called at compile time. MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs); MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); MOZ_MUST_USE bool emitExpressionStatement(UnaryNode* exprStmt); MOZ_MUST_USE bool emitStatementList(ListNode* stmtList); MOZ_MUST_USE bool emitDeleteName(UnaryNode* deleteNode); MOZ_MUST_USE bool emitDeleteProperty(UnaryNode* deleteNode); MOZ_MUST_USE bool emitDeleteElement(UnaryNode* deleteNode); MOZ_MUST_USE bool emitDeleteExpression(UnaryNode* deleteNode); // |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR. MOZ_MUST_USE bool emitTypeof(UnaryNode* typeofNode, JSOp op); MOZ_MUST_USE bool emitUnary(UnaryNode* unaryNode); MOZ_MUST_USE bool emitRightAssociative(ListNode* node); MOZ_MUST_USE bool emitLeftAssociative(ListNode* node); MOZ_MUST_USE bool emitLogical(ListNode* node); MOZ_MUST_USE bool emitSequenceExpr(ListNode* node, ValueUsage valueUsage = ValueUsage::WantValue); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec); MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional, ValueUsage valueUsage = ValueUsage::WantValue); bool isRestParameter(ParseNode* expr); MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread, CallOrNewEmitter& cone); MOZ_MUST_USE bool emitCallOrNew(BinaryNode* callNode, ValueUsage valueUsage = ValueUsage::WantValue); MOZ_MUST_USE bool emitSelfHostedCallFunction(BinaryNode* callNode); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(); MOZ_MUST_USE bool emitSelfHostedAllowContentIter(BinaryNode* callNode); MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(BinaryNode* callNode); MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(BinaryNode* callNode); MOZ_MUST_USE bool emitSelfHostedHasOwn(BinaryNode* callNode); MOZ_MUST_USE bool emitDo(BinaryNode* doNode); MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); MOZ_MUST_USE bool emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); MOZ_MUST_USE bool emitBreak(PropertyName* label); MOZ_MUST_USE bool emitContinue(PropertyName* label); MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody); MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody); MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name); // Emit bytecode for the spread operator. // // emitSpread expects the current index (I) of the array, the array itself // and the iterator to be on the stack in that order (iterator on the bottom). // It will pop the iterator and I, then iterate over the iterator by calling // |.next()| and put the results into the I-th element of array with // incrementing I, then push the result I (it will be original I + // iteration count). The stack after iteration will look like |ARRAY INDEX|. MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false); MOZ_MUST_USE bool emitClass(ClassNode* classNode); MOZ_MUST_USE bool emitSuperElemOperands(PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get); MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, bool isCall = false); MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone); MOZ_MUST_USE bool emitPipeline(ListNode* node); MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode); }; class MOZ_RAII AutoCheckUnstableEmitterScope { #ifdef DEBUG bool prev_; BytecodeEmitter* bce_; #endif public: AutoCheckUnstableEmitterScope() = delete; explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce) #ifdef DEBUG : bce_(bce) #endif { #ifdef DEBUG prev_ = bce_->unstableEmitterScope; bce_->unstableEmitterScope = true; #endif } ~AutoCheckUnstableEmitterScope() { #ifdef DEBUG bce_->unstableEmitterScope = prev_; #endif } }; } /* namespace frontend */ } /* namespace js */ #endif /* frontend_BytecodeEmitter_h */ diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp --- a/js/src/frontend/TryEmitter.cpp +++ b/js/src/frontend/TryEmitter.cpp @@ -1,304 +1,304 @@ /* -*- 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/. */ #include "frontend/TryEmitter.h" #include "frontend/BytecodeEmitter.h" #include "frontend/SourceNotes.h" #include "vm/Opcodes.h" using namespace js; using namespace js::frontend; using mozilla::Maybe; TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind) : bce_(bce), kind_(kind), controlKind_(controlKind), depth_(0), noteIndex_(0), tryStart_(0) #ifdef DEBUG , state_(State::Start) #endif { if (controlKind_ == ControlKind::Syntactic) { controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); } finallyStart_.offset = 0; } // Emits JSOP_GOTO to the end of try-catch-finally. // Used in `yield*`. bool TryEmitter::emitJumpOverCatchAndFinally() { if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) { return false; } return true; } bool TryEmitter::emitTry() { MOZ_ASSERT(state_ == State::Start); // 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. depth_ = bce_->stackDepth; // Record the try location, then emit the try block. if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) { return false; } if (!bce_->emit1(JSOP_TRY)) { return false; } tryStart_ = bce_->offset(); #ifdef DEBUG state_ = State::Try; #endif return true; } bool TryEmitter::emitTryEnd() { MOZ_ASSERT(state_ == State::Try); MOZ_ASSERT(depth_ == bce_->stackDepth); // GOSUB to finally, if present. if (hasFinally() && controlInfo_) { - if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) { + if (!bce_->emitGoSub(&controlInfo_->gosubs)) { return false; } } // Source note points to the jump at the end of the try block. if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::Try::EndOfTryJumpOffset, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH)) { return false; } // Emit jump over catch and/or finally. if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) { return false; } if (!bce_->emitJumpTarget(&tryEnd_)) { return false; } return true; } bool TryEmitter::emitCatch() { MOZ_ASSERT(state_ == State::Try); if (!emitTryEnd()) { return false; } MOZ_ASSERT(bce_->stackDepth == depth_); if (controlKind_ == ControlKind::Syntactic) { // 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 (!bce_->emit1(JSOP_UNDEFINED)) { return false; } if (!bce_->emit1(JSOP_SETRVAL)) { return false; } } #ifdef DEBUG state_ = State::Catch; #endif return true; } bool TryEmitter::emitCatchEnd() { MOZ_ASSERT(state_ == State::Catch); if (!controlInfo_) { return true; } // gosub , if required. if (hasFinally()) { - if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) { + if (!bce_->emitGoSub(&controlInfo_->gosubs)) { return false; } MOZ_ASSERT(bce_->stackDepth == depth_); // Jump over the finally block. if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) { return false; } } return true; } bool TryEmitter::emitFinally(const Maybe& finallyPos /* = Nothing() */) { // If we are using controlInfo_ (i.e., emitting a syntactic try // blocks), we must have specified up front if there will be a finally // close. For internal non-syntactic try blocks, like those emitted for // yield* and IteratorClose inside for-of loops, we can emitFinally even // without specifying up front, since the internal non-syntactic try // blocks emit no GOSUBs. if (!controlInfo_) { if (kind_ == Kind::TryCatch) { kind_ = Kind::TryCatchFinally; } } else { MOZ_ASSERT(hasFinally()); } if (!hasCatch()) { MOZ_ASSERT(state_ == State::Try); if (!emitTryEnd()) { return false; } } else { MOZ_ASSERT(state_ == State::Catch); if (!emitCatchEnd()) { return false; } } MOZ_ASSERT(bce_->stackDepth == depth_); if (!bce_->emitJumpTarget(&finallyStart_)) { return false; } if (controlInfo_) { // Fix up the gosubs that might have been emitted before non-local // jumps to the finally code. bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_); // Indicate that we're emitting a subroutine body. controlInfo_->setEmittingSubroutine(); } if (finallyPos) { if (!bce_->updateSourceCoordNotes(finallyPos.value())) { return false; } } if (!bce_->emit1(JSOP_FINALLY)) { return false; } if (controlKind_ == ControlKind::Syntactic) { if (!bce_->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 (!bce_->emit1(JSOP_UNDEFINED)) { return false; } if (!bce_->emit1(JSOP_SETRVAL)) { return false; } } #ifdef DEBUG state_ = State::Finally; #endif return true; } bool TryEmitter::emitFinallyEnd() { MOZ_ASSERT(state_ == State::Finally); if (controlKind_ == ControlKind::Syntactic) { if (!bce_->emit1(JSOP_SETRVAL)) { return false; } } if (!bce_->emit1(JSOP_RETSUB)) { return false; } bce_->hasTryFinally = true; return true; } bool TryEmitter::emitEnd() { if (!hasFinally()) { MOZ_ASSERT(state_ == State::Catch); if (!emitCatchEnd()) { return false; } } else { MOZ_ASSERT(state_ == State::Finally); if (!emitFinallyEnd()) { return false; } } MOZ_ASSERT(bce_->stackDepth == depth_); // ReconstructPCStack needs a NOP here to mark the end of the last // catch block. if (!bce_->emit1(JSOP_NOP)) { return false; } // Fix up the end-of-try/catch jumps to come here. if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) { return false; } // 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 (hasCatch()) { if (!bce_->addTryNote(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset)) { 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 (hasFinally()) { if (!bce_->addTryNote(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset)) { return false; } } #ifdef DEBUG state_ = State::End; #endif return true; } diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1,5459 +1,5477 @@ /* -*- 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/. */ #include "jit/BaselineCompiler.h" #include "mozilla/Casting.h" #include "jit/BaselineIC.h" #include "jit/BaselineJIT.h" #include "jit/FixedList.h" #include "jit/IonAnalysis.h" #include "jit/JitcodeMap.h" #include "jit/JitSpewer.h" #include "jit/Linker.h" #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif #include "jit/SharedICHelpers.h" #include "jit/VMFunctions.h" #include "js/UniquePtr.h" #include "vm/AsyncFunction.h" #include "vm/AsyncIteration.h" #include "vm/EnvironmentObject.h" #include "vm/Interpreter.h" #include "vm/JSFunction.h" #include "vm/TraceLogging.h" #include "vtune/VTuneWrapper.h" #include "jit/BaselineFrameInfo-inl.h" #include "jit/MacroAssembler-inl.h" #include "vm/Interpreter-inl.h" #include "vm/JSScript-inl.h" #include "vm/NativeObject-inl.h" #include "vm/TypeInference-inl.h" using namespace js; using namespace js::jit; using mozilla::AssertedCast; BaselineCompiler::BaselineCompiler(JSContext* cx, TempAllocator& alloc, JSScript* script) : cx(cx), script(script), pc(script->code()), ionCompileable_(jit::IsIonEnabled(cx) && CanIonCompileScript(cx, script)), compileDebugInstrumentation_(script->isDebuggee()), alloc_(alloc), analysis_(alloc, script), frame(script, masm), stubSpace_(), icEntries_(), pcMappingEntries_(), icLoadLabels_(), pushedBeforeCall_(0), #ifdef DEBUG inCall_(false), #endif profilerPushToggleOffset_(), profilerEnterFrameToggleOffset_(), profilerExitFrameToggleOffset_(), traceLoggerToggleOffsets_(cx), traceLoggerScriptTextIdOffset_(), modifiesArguments_(false) { #ifdef JS_CODEGEN_NONE MOZ_CRASH(); #endif } bool BaselineCompiler::init() { if (!analysis_.init(alloc_, cx->caches().gsnCache)) { return false; } if (!labels_.init(alloc_, script->length())) { return false; } for (size_t i = 0; i < script->length(); i++) { new (&labels_[i]) Label(); } if (!frame.init(alloc_)) { return false; } return true; } bool BaselineCompiler::addPCMappingEntry(bool addIndexEntry) { // Don't add multiple entries for a single pc. size_t nentries = pcMappingEntries_.length(); if (nentries > 0 && pcMappingEntries_[nentries - 1].pcOffset == script->pcToOffset(pc)) { return true; } PCMappingEntry entry; entry.pcOffset = script->pcToOffset(pc); entry.nativeOffset = masm.currentOffset(); entry.slotInfo = getStackTopSlotInfo(); entry.addIndexEntry = addIndexEntry; return pcMappingEntries_.append(entry); } MethodStatus BaselineCompiler::compile() { JitSpew(JitSpew_BaselineScripts, "Baseline compiling script %s:%u:%u (%p)", script->filename(), script->lineno(), script->column(), script); JitSpew(JitSpew_Codegen, "# Emitting baseline code for script %s:%u:%u", script->filename(), script->lineno(), script->column()); TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); TraceLoggerEvent scriptEvent(TraceLogger_AnnotateScripts, script); AutoTraceLog logScript(logger, scriptEvent); AutoTraceLog logCompile(logger, TraceLogger_BaselineCompilation); AutoKeepTypeScripts keepTypes(cx); if (!script->ensureHasTypes(cx, keepTypes) || !script->ensureHasAnalyzedArgsUsage(cx)) { return Method_Error; } // When code coverage is only enabled for optimizations, or when a Debugger // set the collectCoverageInfo flag, we have to create the ScriptCounts if // they do not exist. if (!script->hasScriptCounts() && cx->realm()->collectCoverage()) { if (!script->initScriptCounts(cx)) { return Method_Error; } } // Pin analysis info during compilation. AutoEnterAnalysis autoEnterAnalysis(cx); MOZ_ASSERT(!script->hasBaselineScript()); if (!emitPrologue()) { return Method_Error; } MethodStatus status = emitBody(); if (status != Method_Compiled) { return status; } if (!emitEpilogue()) { return Method_Error; } if (!emitOutOfLinePostBarrierSlot()) { return Method_Error; } Linker linker(masm); if (masm.oom()) { ReportOutOfMemory(cx); return Method_Error; } AutoFlushICache afc("Baseline"); JitCode* code = linker.newCode(cx, CodeKind::Baseline); if (!code) { return Method_Error; } Rooted templateEnv(cx); if (script->functionNonDelazifying()) { RootedFunction fun(cx, script->functionNonDelazifying()); if (fun->needsNamedLambdaEnvironment()) { templateEnv = NamedLambdaObject::createTemplateObject(cx, fun, gc::TenuredHeap); if (!templateEnv) { return Method_Error; } } if (fun->needsCallObject()) { RootedScript scriptRoot(cx, script); templateEnv = CallObject::createTemplateObject(cx, scriptRoot, templateEnv, gc::TenuredHeap); if (!templateEnv) { return Method_Error; } } } // Encode the pc mapping table. See PCMappingIndexEntry for // more information. Vector pcMappingIndexEntries(cx); CompactBufferWriter pcEntries; uint32_t previousOffset = 0; for (size_t i = 0; i < pcMappingEntries_.length(); i++) { PCMappingEntry& entry = pcMappingEntries_[i]; if (entry.addIndexEntry) { PCMappingIndexEntry indexEntry; indexEntry.pcOffset = entry.pcOffset; indexEntry.nativeOffset = entry.nativeOffset; indexEntry.bufferOffset = pcEntries.length(); if (!pcMappingIndexEntries.append(indexEntry)) { ReportOutOfMemory(cx); return Method_Error; } previousOffset = entry.nativeOffset; } // Use the high bit of the SlotInfo byte to indicate the // native code offset (relative to the previous op) > 0 and // comes next in the buffer. MOZ_ASSERT((entry.slotInfo.toByte() & 0x80) == 0); if (entry.nativeOffset == previousOffset) { pcEntries.writeByte(entry.slotInfo.toByte()); } else { MOZ_ASSERT(entry.nativeOffset > previousOffset); pcEntries.writeByte(0x80 | entry.slotInfo.toByte()); pcEntries.writeUnsigned(entry.nativeOffset - previousOffset); } previousOffset = entry.nativeOffset; } if (pcEntries.oom()) { ReportOutOfMemory(cx); return Method_Error; } // Note: There is an extra entry in the bytecode type map for the search // hint, see below. size_t bytecodeTypeMapEntries = script->nTypeSets() + 1; size_t resumeEntries = script->hasResumeOffsets() ? script->resumeOffsets().size() : 0; UniquePtr baselineScript( BaselineScript::New(script, bailoutPrologueOffset_.offset(), debugOsrPrologueOffset_.offset(), debugOsrEpilogueOffset_.offset(), profilerEnterFrameToggleOffset_.offset(), profilerExitFrameToggleOffset_.offset(), icEntries_.length(), retAddrEntries_.length(), pcMappingIndexEntries.length(), pcEntries.length(), bytecodeTypeMapEntries, resumeEntries, traceLoggerToggleOffsets_.length()), JS::DeletePolicy(cx->runtime())); if (!baselineScript) { ReportOutOfMemory(cx); return Method_Error; } baselineScript->setMethod(code); baselineScript->setTemplateEnvironment(templateEnv); JitSpew(JitSpew_BaselineScripts, "Created BaselineScript %p (raw %p) for %s:%u:%u", (void*) baselineScript.get(), (void*) code->raw(), script->filename(), script->lineno(), script->column()); MOZ_ASSERT(pcMappingIndexEntries.length() > 0); baselineScript->copyPCMappingIndexEntries(&pcMappingIndexEntries[0]); MOZ_ASSERT(pcEntries.length() > 0); baselineScript->copyPCMappingEntries(pcEntries); // Copy ICEntries and RetAddrEntries. if (icEntries_.length() > 0) { baselineScript->copyICEntries(script, &icEntries_[0]); } if (retAddrEntries_.length() > 0) { baselineScript->copyRetAddrEntries(script, &retAddrEntries_[0]); } // Adopt fallback stubs from the compiler into the baseline script. baselineScript->adoptFallbackStubs(&stubSpace_); // If profiler instrumentation is enabled, toggle instrumentation on. if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { baselineScript->toggleProfilerInstrumentation(true); } // Patch IC loads using IC entries. for (size_t i = 0; i < icLoadLabels_.length(); i++) { CodeOffset label = icLoadLabels_[i].label; size_t icEntry = icLoadLabels_[i].icEntry; ICEntry* entryAddr = &(baselineScript->icEntry(icEntry)); Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, label), ImmPtr(entryAddr), ImmPtr((void*)-1)); } if (modifiesArguments_) { baselineScript->setModifiesArguments(); } if (analysis_.usesEnvironmentChain()) { baselineScript->setUsesEnvironmentChain(); } #ifdef JS_TRACE_LOGGING // Initialize the tracelogger instrumentation. baselineScript->initTraceLogger(script, traceLoggerToggleOffsets_); #endif uint32_t* bytecodeMap = baselineScript->bytecodeTypeMap(); FillBytecodeTypeMap(script, bytecodeMap); // The last entry in the last index found, and is used to avoid binary // searches for the sought entry when queries are in linear order. bytecodeMap[script->nTypeSets()] = 0; // Compute yield/await native resume addresses. baselineScript->computeResumeNativeOffsets(script); if (compileDebugInstrumentation_) { baselineScript->setHasDebugInstrumentation(); } // Always register a native => bytecode mapping entry, since profiler can be // turned on with baseline jitcode on stack, and baseline jitcode cannot be invalidated. { JitSpew(JitSpew_Profiling, "Added JitcodeGlobalEntry for baseline script %s:%u:%u (%p)", script->filename(), script->lineno(), script->column(), baselineScript.get()); // Generate profiling string. char* str = JitcodeGlobalEntry::createScriptString(cx, script); if (!str) { return Method_Error; } JitcodeGlobalEntry::BaselineEntry entry; entry.init(code, code->raw(), code->rawEnd(), script, str); JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); if (!globalTable->addEntry(entry)) { entry.destroy(); ReportOutOfMemory(cx); return Method_Error; } // Mark the jitcode as having a bytecode map. code->setHasBytecodeMap(); } script->setBaselineScript(cx->runtime(), baselineScript.release()); #ifdef JS_ION_PERF writePerfSpewerBaselineProfile(script, code); #endif #ifdef MOZ_VTUNE vtune::MarkScript(code, script, "baseline"); #endif return Method_Compiled; } void BaselineCompiler::emitInitializeLocals() { // Initialize all locals to |undefined|. Lexical bindings are temporal // dead zoned in bytecode. size_t n = frame.nlocals(); if (n == 0) { return; } // Use R0 to minimize code size. If the number of locals to push is < // LOOP_UNROLL_FACTOR, then the initialization pushes are emitted directly // and inline. Otherwise, they're emitted in a partially unrolled loop. static const size_t LOOP_UNROLL_FACTOR = 4; size_t toPushExtra = n % LOOP_UNROLL_FACTOR; masm.moveValue(UndefinedValue(), R0); // Handle any extra pushes left over by the optional unrolled loop below. for (size_t i = 0; i < toPushExtra; i++) { masm.pushValue(R0); } // Partially unrolled loop of pushes. if (n >= LOOP_UNROLL_FACTOR) { size_t toPush = n - toPushExtra; MOZ_ASSERT(toPush % LOOP_UNROLL_FACTOR == 0); MOZ_ASSERT(toPush >= LOOP_UNROLL_FACTOR); masm.move32(Imm32(toPush), R1.scratchReg()); // Emit unrolled loop with 4 pushes per iteration. Label pushLoop; masm.bind(&pushLoop); for (size_t i = 0; i < LOOP_UNROLL_FACTOR; i++) { masm.pushValue(R0); } masm.branchSub32(Assembler::NonZero, Imm32(LOOP_UNROLL_FACTOR), R1.scratchReg(), &pushLoop); } } bool BaselineCompiler::emitPrologue() { #ifdef JS_USE_LINK_REGISTER // Push link register from generateEnterJIT()'s BLR. masm.pushReturnAddress(); masm.checkStackAlignment(); #endif emitProfilerEnterFrame(); if (script->trackRecordReplayProgress()) { masm.inc64(AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter())); } masm.push(BaselineFrameReg); masm.moveStackPtrTo(BaselineFrameReg); masm.subFromStackPtr(Imm32(BaselineFrame::Size())); // Initialize BaselineFrame. For eval scripts, the env chain // is passed in R1, so we have to be careful not to clobber it. // Initialize BaselineFrame::flags. masm.store32(Imm32(0), frame.addressOfFlags()); // Handle env chain pre-initialization (in case GC gets run // during stack check). For global and eval scripts, the env // chain is in R1. For function scripts, the env chain is in // the callee, nullptr is stored for now so that GC doesn't choke // on a bogus EnvironmentChain value in the frame. if (function()) { masm.storePtr(ImmPtr(nullptr), frame.addressOfEnvironmentChain()); } else { masm.storePtr(R1.scratchReg(), frame.addressOfEnvironmentChain()); } // Functions with a large number of locals require two stack checks. // The VMCall for a fallible stack check can only occur after the // env chain has been initialized, as that is required for proper // exception handling if the VMCall returns false. The env chain // initialization can only happen after the UndefinedValues for the // local slots have been pushed. However by that time, the stack might // have grown too much. // // In these cases, we emit an extra, early, infallible check before pushing // the locals. The early check just sets a flag on the frame if the stack // check fails. If the flag is set, then the jitcode skips past the pushing // of the locals, and directly to env chain initialization followed by the // actual stack check, which will throw the correct exception. Label earlyStackCheckFailed; if (needsEarlyStackCheck()) { // Subtract the size of script->nslots() from the stack pointer. uint32_t slotsSize = script->nslots() * sizeof(Value); Register scratch = R1.scratchReg(); masm.moveStackPtrTo(scratch); masm.subPtr(Imm32(slotsSize), scratch); // Set the OVER_RECURSED flag on the frame if the computed stack pointer // overflows the stack limit. We have to use the actual (*NoInterrupt) // stack limit here because we don't want to set the flag and throw an // overrecursion exception later in the interrupt case. Label stackCheckOk; masm.branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(cx->addressOfJitStackLimitNoInterrupt()), scratch, &stackCheckOk); { masm.or32(Imm32(BaselineFrame::OVER_RECURSED), frame.addressOfFlags()); masm.jump(&earlyStackCheckFailed); } masm.bind(&stackCheckOk); } emitInitializeLocals(); if (needsEarlyStackCheck()) { masm.bind(&earlyStackCheckFailed); } #ifdef JS_TRACE_LOGGING if (!emitTraceLoggerEnter()) { return false; } #endif // Record the offset of the prologue, because Ion can bailout before // the env chain is initialized. bailoutPrologueOffset_ = CodeOffset(masm.currentOffset()); // When compiling with Debugger instrumentation, set the debuggeeness of // the frame before any operation that can call into the VM. emitIsDebuggeeCheck(); // Initialize the env chain before any operation that may // call into the VM and trigger a GC. if (!initEnvironmentChain()) { return false; } frame.assertSyncedStack(); masm.debugAssertContextRealm(script->realm(), R1.scratchReg()); if (!emitStackCheck()) { return false; } if (!emitDebugPrologue()) { return false; } if (!emitWarmUpCounterIncrement()) { return false; } if (!emitArgumentTypeChecks()) { return false; } return true; } bool BaselineCompiler::emitEpilogue() { // Record the offset of the epilogue, so we can do early return from // Debugger handlers during on-stack recompile. debugOsrEpilogueOffset_ = CodeOffset(masm.currentOffset()); masm.bind(&return_); #ifdef JS_TRACE_LOGGING if (!emitTraceLoggerExit()) { return false; } #endif masm.moveToStackPtr(BaselineFrameReg); masm.pop(BaselineFrameReg); emitProfilerExitFrame(); masm.ret(); return true; } // On input: // R2.scratchReg() contains object being written to. // Called with the baseline stack synced, except for R0 which is preserved. // All other registers are usable as scratch. // This calls: // void PostWriteBarrier(JSRuntime* rt, JSObject* obj); bool BaselineCompiler::emitOutOfLinePostBarrierSlot() { masm.bind(&postBarrierSlot_); Register objReg = R2.scratchReg(); AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(R0); regs.take(objReg); regs.take(BaselineFrameReg); Register scratch = regs.takeAny(); #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) // On ARM, save the link register before calling. It contains the return // address. The |masm.ret()| later will pop this into |pc| to return. masm.push(lr); #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) masm.push(ra); #endif masm.pushValue(R0); masm.setupUnalignedABICall(scratch); masm.movePtr(ImmPtr(cx->runtime()), scratch); masm.passABIArg(scratch); masm.passABIArg(objReg); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); masm.popValue(R0); masm.ret(); return true; } bool BaselineCompiler::emitIC(ICStub* stub, bool isForOp) { MOZ_ASSERT_IF(isForOp, BytecodeOpHasIC(JSOp(*pc))); if (!stub) { return false; } CodeOffset patchOffset, callOffset; EmitCallIC(masm, &patchOffset, &callOffset); // ICs need both an ICEntry and a RetAddrEntry. RetAddrEntry::Kind kind = isForOp ? RetAddrEntry::Kind::IC : RetAddrEntry::Kind::NonOpIC; if (!retAddrEntries_.emplaceBack(script->pcToOffset(pc), kind, callOffset)) { ReportOutOfMemory(cx); return false; } if (!icEntries_.emplaceBack(stub, script->pcToOffset(pc), isForOp)) { ReportOutOfMemory(cx); return false; } if (!addICLoadLabel(patchOffset)) { return false; } return true; } void BaselineCompiler::prepareVMCall() { pushedBeforeCall_ = masm.framePushed(); #ifdef DEBUG inCall_ = true; #endif // Ensure everything is synced. frame.syncStack(0); // Save the frame pointer. masm.Push(BaselineFrameReg); } bool BaselineCompiler::callVM(const VMFunction& fun, CallVMPhase phase) { TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(fun); #ifdef DEBUG // Assert prepareVMCall() has been called. MOZ_ASSERT(inCall_); inCall_ = false; // Assert the frame does not have an override pc when we're executing JIT code. { Label ok; masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), Imm32(BaselineFrame::HAS_OVERRIDE_PC), &ok); masm.assumeUnreachable("BaselineFrame shouldn't override pc when executing JIT code"); masm.bind(&ok); } #endif // Compute argument size. Note that this include the size of the frame pointer // pushed by prepareVMCall. uint32_t argSize = fun.explicitStackSlots() * sizeof(void*) + sizeof(void*); // Assert all arguments were pushed. MOZ_ASSERT(masm.framePushed() - pushedBeforeCall_ == argSize); Address frameSizeAddress(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize()); uint32_t frameVals = frame.nlocals() + frame.stackDepth(); uint32_t frameBaseSize = BaselineFrame::FramePointerOffset + BaselineFrame::Size(); uint32_t frameFullSize = frameBaseSize + (frameVals * sizeof(Value)); if (phase == POST_INITIALIZE) { masm.store32(Imm32(frameFullSize), frameSizeAddress); uint32_t descriptor = MakeFrameDescriptor(frameFullSize + argSize, FrameType::BaselineJS, ExitFrameLayout::Size()); masm.push(Imm32(descriptor)); } else { MOZ_ASSERT(phase == CHECK_OVER_RECURSED); Label afterWrite; Label writePostInitialize; // If OVER_RECURSED is set, then frame locals haven't been pushed yet. masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), Imm32(BaselineFrame::OVER_RECURSED), &writePostInitialize); masm.move32(Imm32(frameBaseSize), ICTailCallReg); masm.jump(&afterWrite); masm.bind(&writePostInitialize); masm.move32(Imm32(frameFullSize), ICTailCallReg); masm.bind(&afterWrite); masm.store32(ICTailCallReg, frameSizeAddress); masm.add32(Imm32(argSize), ICTailCallReg); masm.makeFrameDescriptor(ICTailCallReg, FrameType::BaselineJS, ExitFrameLayout::Size()); masm.push(ICTailCallReg); } MOZ_ASSERT(fun.expectTailCall == NonTailCall); // Perform the call. masm.call(code); uint32_t callOffset = masm.currentOffset(); masm.pop(BaselineFrameReg); #ifdef DEBUG // Assert the frame does not have an override pc when we're executing JIT code. { Label ok; masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), Imm32(BaselineFrame::HAS_OVERRIDE_PC), &ok); masm.assumeUnreachable("BaselineFrame shouldn't override pc after VM call"); masm.bind(&ok); } #endif return appendRetAddrEntry(RetAddrEntry::Kind::CallVM, callOffset); } typedef bool (*CheckOverRecursedBaselineFn)(JSContext*, BaselineFrame*); static const VMFunction CheckOverRecursedBaselineInfo = FunctionInfo(CheckOverRecursedBaseline, "CheckOverRecursedBaseline"); bool BaselineCompiler::emitStackCheck() { // If this is the late stack check for a frame which contains an early stack check, // then the early stack check might have failed and skipped past the pushing of locals // on the stack. // // If this is a possibility, then the OVER_RECURSED flag should be checked, and the // VMCall to CheckOverRecursedBaseline done unconditionally if it's set. Label forceCall; if (needsEarlyStackCheck()) { masm.branchTest32(Assembler::NonZero, frame.addressOfFlags(), Imm32(BaselineFrame::OVER_RECURSED), &forceCall); } Label skipCall; masm.branchStackPtrRhs(Assembler::BelowOrEqual, AbsoluteAddress(cx->addressOfJitStackLimit()), &skipCall); if (needsEarlyStackCheck()) { masm.bind(&forceCall); } prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); pushArg(R1.scratchReg()); CallVMPhase phase = POST_INITIALIZE; if (needsEarlyStackCheck()) { phase = CHECK_OVER_RECURSED; } if (!callVMNonOp(CheckOverRecursedBaselineInfo, phase)) { return false; } retAddrEntries_.back().setKind(RetAddrEntry::Kind::StackCheck); masm.bind(&skipCall); return true; } void BaselineCompiler::emitIsDebuggeeCheck() { if (compileDebugInstrumentation_) { masm.Push(BaselineFrameReg); masm.setupUnalignedABICall(R0.scratchReg()); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); masm.passABIArg(R0.scratchReg()); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, jit::FrameIsDebuggeeCheck)); masm.Pop(BaselineFrameReg); } } typedef bool (*DebugPrologueFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*); static const VMFunction DebugPrologueInfo = FunctionInfo(jit::DebugPrologue, "DebugPrologue"); bool BaselineCompiler::emitDebugPrologue() { if (compileDebugInstrumentation_) { // Load pointer to BaselineFrame in R0. masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); prepareVMCall(); pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); if (!callVM(DebugPrologueInfo)) { return false; } // Fix up the RetAddrEntry appended by callVM for on-stack recompilation. retAddrEntries_.back().setKind(RetAddrEntry::Kind::DebugPrologue); // If the stub returns |true|, we have to return the value stored in the // frame's return value slot. Label done; masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); { masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); masm.jump(&return_); } masm.bind(&done); } debugOsrPrologueOffset_ = CodeOffset(masm.currentOffset()); return true; } typedef bool (*CheckGlobalOrEvalDeclarationConflictsFn)(JSContext*, BaselineFrame*); static const VMFunction CheckGlobalOrEvalDeclarationConflictsInfo = FunctionInfo(jit::CheckGlobalOrEvalDeclarationConflicts, "CheckGlobalOrEvalDeclarationConflicts"); typedef bool (*InitFunctionEnvironmentObjectsFn)(JSContext*, BaselineFrame*); static const VMFunction InitFunctionEnvironmentObjectsInfo = FunctionInfo(jit::InitFunctionEnvironmentObjects, "InitFunctionEnvironmentObjects"); bool BaselineCompiler::initEnvironmentChain() { CallVMPhase phase = POST_INITIALIZE; if (needsEarlyStackCheck()) { phase = CHECK_OVER_RECURSED; } RootedFunction fun(cx, function()); if (fun) { // Use callee->environment as env chain. Note that we do this also // for needsSomeEnvironmentObject functions, so that the env chain // slot is properly initialized if the call triggers GC. Register callee = R0.scratchReg(); Register scope = R1.scratchReg(); masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), callee); masm.loadPtr(Address(callee, JSFunction::offsetOfEnvironment()), scope); masm.storePtr(scope, frame.addressOfEnvironmentChain()); if (fun->needsFunctionEnvironmentObjects()) { // Call into the VM to create the proper environment objects. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVMNonOp(InitFunctionEnvironmentObjectsInfo, phase)) { return false; } } } else if (module()) { // Modules use a pre-created scope object. Register scope = R1.scratchReg(); masm.movePtr(ImmGCPtr(&module()->initialEnvironment()), scope); masm.storePtr(scope, frame.addressOfEnvironmentChain()); } else { // EnvironmentChain pointer in BaselineFrame has already been initialized // in prologue, but we need to check for redeclaration errors. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVMNonOp(CheckGlobalOrEvalDeclarationConflictsInfo, phase)) { return false; } } return true; } typedef bool (*InterruptCheckFn)(JSContext*); static const VMFunction InterruptCheckInfo = FunctionInfo(InterruptCheck, "InterruptCheck"); bool BaselineCompiler::emitInterruptCheck() { frame.syncStack(0); Label done; masm.branch32(Assembler::Equal, AbsoluteAddress(cx->addressOfInterruptBits()), Imm32(0), &done); prepareVMCall(); if (!callVM(InterruptCheckInfo)) { return false; } masm.bind(&done); return true; } typedef bool (*IonCompileScriptForBaselineFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction IonCompileScriptForBaselineInfo = FunctionInfo(IonCompileScriptForBaseline, "IonCompileScriptForBaseline"); bool BaselineCompiler::emitWarmUpCounterIncrement(bool allowOsr) { // Emit no warm-up counter increments or bailouts if Ion is not // enabled, or if the script will never be Ion-compileable if (!ionCompileable_) { return true; } frame.assertSyncedStack(); Register scriptReg = R2.scratchReg(); Register countReg = R0.scratchReg(); Address warmUpCounterAddr(scriptReg, JSScript::offsetOfWarmUpCounter()); masm.movePtr(ImmGCPtr(script), scriptReg); masm.load32(warmUpCounterAddr, countReg); masm.add32(Imm32(1), countReg); masm.store32(countReg, warmUpCounterAddr); // If this is a loop inside a catch or finally block, increment the warmup // counter but don't attempt OSR (Ion only compiles the try block). if (analysis_.info(pc).loopEntryInCatchOrFinally) { MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); return true; } // OSR not possible at this loop entry. if (!allowOsr) { MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); return true; } Label skipCall; const OptimizationInfo* info = IonOptimizations.get(IonOptimizations.firstLevel()); uint32_t warmUpThreshold = info->compilerWarmUpThreshold(script, pc); masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold), &skipCall); masm.branchPtr(Assembler::Equal, Address(scriptReg, JSScript::offsetOfIonScript()), ImmPtr(ION_COMPILING_SCRIPT), &skipCall); // Try to compile and/or finish a compilation. if (JSOp(*pc) == JSOP_LOOPENTRY) { // During the loop entry we can try to OSR into ion. // The ic has logic for this. ICWarmUpCounter_Fallback::Compiler stubCompiler(cx); if (!emitNonOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } } else { // To call stubs we need to have an opcode. This code handles the // prologue and there is no dedicatd opcode present. Therefore use an // annotated vm call. prepareVMCall(); masm.Push(ImmPtr(pc)); masm.PushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); if (!callVM(IonCompileScriptForBaselineInfo)) { return false; } // Annotate the RetAddrEntry as warmup counter. retAddrEntries_.back().setKind(RetAddrEntry::Kind::WarmupCounter); } masm.bind(&skipCall); return true; } bool BaselineCompiler::emitArgumentTypeChecks() { if (!function()) { return true; } frame.pushThis(); frame.popRegsAndSync(1); ICTypeMonitor_Fallback::Compiler compiler(cx, uint32_t(0)); if (!emitNonOpIC(compiler.getStub(&stubSpace_))) { return false; } for (size_t i = 0; i < function()->nargs(); i++) { frame.pushArg(i); frame.popRegsAndSync(1); ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1); if (!emitNonOpIC(compiler.getStub(&stubSpace_))) { return false; } } return true; } bool BaselineCompiler::emitDebugTrap() { MOZ_ASSERT(compileDebugInstrumentation_); MOZ_ASSERT(frame.numUnsyncedSlots() == 0); bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc); #if defined(JS_CODEGEN_ARM64) // Flush any pending constant pools to prevent incorrect // PCMappingEntry offsets. See Bug 1446819. masm.flush(); // Fix up the PCMappingEntry to avoid any constant pool. pcMappingEntries_.back().nativeOffset = masm.currentOffset(); #endif // Emit patchable call to debug trap handler. JitCode* handler = cx->runtime()->jitRuntime()->debugTrapHandler(cx); if (!handler) { return false; } mozilla::DebugOnly offset = masm.toggledCall(handler, enabled); #ifdef DEBUG // Patchable call offset has to match the pc mapping offset. PCMappingEntry& entry = pcMappingEntries_.back(); MOZ_ASSERT((&offset)->offset() == entry.nativeOffset); #endif // Add a RetAddrEntry for the return offset -> pc mapping. return appendRetAddrEntry(RetAddrEntry::Kind::DebugTrap, masm.currentOffset()); } #ifdef JS_TRACE_LOGGING bool BaselineCompiler::emitTraceLoggerEnter() { AllocatableRegisterSet regs(RegisterSet::Volatile()); Register loggerReg = regs.takeAnyGeneral(); Register scriptReg = regs.takeAnyGeneral(); Label noTraceLogger; if (!traceLoggerToggleOffsets_.append(masm.toggledJump(&noTraceLogger))) { return false; } masm.Push(loggerReg); masm.Push(scriptReg); masm.loadTraceLogger(loggerReg); // Script start. masm.movePtr(ImmGCPtr(script), scriptReg); masm.loadPtr(Address(scriptReg, JSScript::offsetOfBaselineScript()), scriptReg); Address scriptEvent(scriptReg, BaselineScript::offsetOfTraceLoggerScriptEvent()); masm.computeEffectiveAddress(scriptEvent, scriptReg); masm.tracelogStartEvent(loggerReg, scriptReg); // Engine start. masm.tracelogStartId(loggerReg, TraceLogger_Baseline, /* force = */ true); masm.Pop(scriptReg); masm.Pop(loggerReg); masm.bind(&noTraceLogger); return true; } bool BaselineCompiler::emitTraceLoggerExit() { AllocatableRegisterSet regs(RegisterSet::Volatile()); Register loggerReg = regs.takeAnyGeneral(); Label noTraceLogger; if (!traceLoggerToggleOffsets_.append(masm.toggledJump(&noTraceLogger))) { return false; } masm.Push(loggerReg); masm.loadTraceLogger(loggerReg); masm.tracelogStopId(loggerReg, TraceLogger_Baseline, /* force = */ true); masm.tracelogStopId(loggerReg, TraceLogger_Scripts, /* force = */ true); masm.Pop(loggerReg); masm.bind(&noTraceLogger); return true; } bool BaselineCompiler::emitTraceLoggerResume(Register baselineScript, AllocatableGeneralRegisterSet& regs) { Register scriptId = regs.takeAny(); Register loggerReg = regs.takeAny(); Label noTraceLogger; if (!traceLoggerToggleOffsets_.append(masm.toggledJump(&noTraceLogger))) { return false; } masm.loadTraceLogger(loggerReg); Address scriptEvent(baselineScript, BaselineScript::offsetOfTraceLoggerScriptEvent()); masm.computeEffectiveAddress(scriptEvent, scriptId); masm.tracelogStartEvent(loggerReg, scriptId); masm.tracelogStartId(loggerReg, TraceLogger_Baseline, /* force = */ true); regs.add(loggerReg); regs.add(scriptId); masm.bind(&noTraceLogger); return true; } #endif void BaselineCompiler::emitProfilerEnterFrame() { // Store stack position to lastProfilingFrame variable, guarded by a toggled jump. // Starts off initially disabled. Label noInstrument; CodeOffset toggleOffset = masm.toggledJump(&noInstrument); masm.profilerEnterFrame(masm.getStackPointer(), R0.scratchReg()); masm.bind(&noInstrument); // Store the start offset in the appropriate location. MOZ_ASSERT(!profilerEnterFrameToggleOffset_.bound()); profilerEnterFrameToggleOffset_ = toggleOffset; } void BaselineCompiler::emitProfilerExitFrame() { // Store previous frame to lastProfilingFrame variable, guarded by a toggled jump. // Starts off initially disabled. Label noInstrument; CodeOffset toggleOffset = masm.toggledJump(&noInstrument); masm.profilerExitFrame(); masm.bind(&noInstrument); // Store the start offset in the appropriate location. MOZ_ASSERT(!profilerExitFrameToggleOffset_.bound()); profilerExitFrameToggleOffset_ = toggleOffset; } MethodStatus BaselineCompiler::emitBody() { MOZ_ASSERT(pc == script->code()); bool lastOpUnreachable = false; uint32_t emittedOps = 0; mozilla::DebugOnly prevpc = pc; while (true) { JSOp op = JSOp(*pc); JitSpew(JitSpew_BaselineOp, "Compiling op @ %d: %s", int(script->pcToOffset(pc)), CodeName[op]); BytecodeInfo* info = analysis_.maybeInfo(pc); // Skip unreachable ops. if (!info) { // Test if last instructions and stop emitting in that case. pc += GetBytecodeLength(pc); if (pc >= script->codeEnd()) { break; } lastOpUnreachable = true; prevpc = pc; continue; } if (info->jumpTarget) { // Fully sync the stack if there are incoming jumps. frame.syncStack(0); frame.setStackDepth(info->stackDepth); masm.bind(labelOf(pc)); } else if (MOZ_UNLIKELY(compileDebugInstrumentation_)) { // Also fully sync the stack if the debugger is enabled. frame.syncStack(0); } else { // At the beginning of any op, at most the top 2 stack-values are unsynced. if (frame.stackDepth() > 2) { frame.syncStack(2); } } frame.assertValidState(*info); // Add a PC -> native mapping entry for the current op. These entries are // used when we need the native code address for a given pc, for instance // for bailouts from Ion, the debugger and exception handling. See // PCMappingIndexEntry for more information. bool addIndexEntry = (pc == script->code() || lastOpUnreachable || emittedOps > 100); if (addIndexEntry) { emittedOps = 0; } if (MOZ_UNLIKELY(!addPCMappingEntry(addIndexEntry))) { ReportOutOfMemory(cx); return Method_Error; } // Emit traps for breakpoints and step mode. if (MOZ_UNLIKELY(compileDebugInstrumentation_) && !emitDebugTrap()) { return Method_Error; } switch (op) { // ===== NOT Yet Implemented ===== case JSOP_FORCEINTERPRETER: // Intentionally not implemented. case JSOP_SETINTRINSIC: // Run-once opcode during self-hosting initialization. - case JSOP_UNUSED126: case JSOP_UNUSED206: case JSOP_LIMIT: // === !! WARNING WARNING WARNING !! === // Do you really want to sacrifice performance by not implementing // this operation in the BaselineCompiler? JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", CodeName[op]); return Method_CantCompile; #define EMIT_OP(OP) \ case OP: \ if (MOZ_UNLIKELY(!this->emit_##OP())) \ return Method_Error; \ break; OPCODE_LIST(EMIT_OP) #undef EMIT_OP } // If the main instruction is not a jump target, then we emit the // corresponding code coverage counter. if (pc == script->main() && !BytecodeIsJumpTarget(op)) { if (!emit_JSOP_JUMPTARGET()) { return Method_Error; } } // Test if last instructions and stop emitting in that case. pc += GetBytecodeLength(pc); if (pc >= script->codeEnd()) { break; } emittedOps++; lastOpUnreachable = false; #ifdef DEBUG prevpc = pc; #endif } MOZ_ASSERT(JSOp(*prevpc) == JSOP_RETRVAL); return Method_Compiled; } bool BaselineCompiler::emit_JSOP_NOP() { return true; } bool BaselineCompiler::emit_JSOP_ITERNEXT() { return true; } bool BaselineCompiler::emit_JSOP_NOP_DESTRUCTURING() { return true; } bool BaselineCompiler::emit_JSOP_TRY_DESTRUCTURING_ITERCLOSE() { return true; } bool BaselineCompiler::emit_JSOP_LABEL() { return true; } bool BaselineCompiler::emit_JSOP_POP() { frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_POPN() { frame.popn(GET_UINT16(pc)); return true; } bool BaselineCompiler::emit_JSOP_DUPAT() { frame.syncStack(0); // DUPAT takes a value on the stack and re-pushes it on top. It's like // GETLOCAL but it addresses from the top of the stack instead of from the // stack frame. int depth = -(GET_UINT24(pc) + 1); masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_DUP() { // Keep top stack value in R0, sync the rest so that we can use R1. We use // separate registers because every register can be used by at most one // StackValue. frame.popRegsAndSync(1); masm.moveValue(R0, R1); // inc/dec ops use DUP followed by ONE, ADD. Push R0 last to avoid a move. frame.push(R1); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_DUP2() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); frame.push(R0); frame.push(R1); return true; } bool BaselineCompiler::emit_JSOP_SWAP() { // Keep top stack values in R0 and R1. frame.popRegsAndSync(2); frame.push(R1); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_PICK() { frame.syncStack(0); // Pick takes a value on the stack and moves it to the top. // For instance, pick 2: // before: A B C D E // after : A B D E C // First, move value at -(amount + 1) into R0. int32_t depth = -(GET_INT8(pc) + 1); masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); // Move the other values down. depth++; for (; depth < 0; depth++) { Address source = frame.addressOfStackValue(frame.peek(depth)); Address dest = frame.addressOfStackValue(frame.peek(depth - 1)); masm.loadValue(source, R1); masm.storeValue(R1, dest); } // Push R0. frame.pop(); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_UNPICK() { frame.syncStack(0); // Pick takes the top of the stack value and moves it under the nth value. // For instance, unpick 2: // before: A B C D E // after : A B E C D // First, move value at -1 into R0. masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); // Move the other values up. int32_t depth = -(GET_INT8(pc) + 1); for (int32_t i = -1; i > depth; i--) { Address source = frame.addressOfStackValue(frame.peek(i - 1)); Address dest = frame.addressOfStackValue(frame.peek(i)); masm.loadValue(source, R1); masm.storeValue(R1, dest); } // Store R0 under the nth value. Address dest = frame.addressOfStackValue(frame.peek(depth)); masm.storeValue(R0, dest); return true; } bool BaselineCompiler::emit_JSOP_GOTO() { frame.syncStack(0); jsbytecode* target = pc + GET_JUMP_OFFSET(pc); masm.jump(labelOf(target)); return true; } bool BaselineCompiler::emitToBoolean() { Label skipIC; masm.branchTestBoolean(Assembler::Equal, R0, &skipIC); // Call IC ICToBool_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } masm.bind(&skipIC); return true; } bool BaselineCompiler::emitTest(bool branchIfTrue) { bool knownBoolean = frame.peek(-1)->isKnownBoolean(); // Keep top stack value in R0. frame.popRegsAndSync(1); if (!knownBoolean && !emitToBoolean()) { return false; } // IC will leave a BooleanValue in R0, just need to branch on it. masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc))); return true; } bool BaselineCompiler::emit_JSOP_IFEQ() { return emitTest(false); } bool BaselineCompiler::emit_JSOP_IFNE() { return emitTest(true); } bool BaselineCompiler::emitAndOr(bool branchIfTrue) { bool knownBoolean = frame.peek(-1)->isKnownBoolean(); // AND and OR leave the original value on the stack. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); if (!knownBoolean && !emitToBoolean()) { return false; } masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc))); return true; } bool BaselineCompiler::emit_JSOP_AND() { return emitAndOr(false); } bool BaselineCompiler::emit_JSOP_OR() { return emitAndOr(true); } bool BaselineCompiler::emit_JSOP_NOT() { bool knownBoolean = frame.peek(-1)->isKnownBoolean(); // Keep top stack value in R0. frame.popRegsAndSync(1); if (!knownBoolean && !emitToBoolean()) { return false; } masm.notBoolean(R0); frame.push(R0, JSVAL_TYPE_BOOLEAN); return true; } bool BaselineCompiler::emit_JSOP_POS() { // Keep top stack value in R0. frame.popRegsAndSync(1); // Inline path for int32 and double. Label done; masm.branchTestNumber(Assembler::Equal, R0, &done); // Call IC. ICToNumber_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } masm.bind(&done); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_LOOPHEAD() { if (!emit_JSOP_JUMPTARGET()) { return false; } return emitInterruptCheck(); } bool BaselineCompiler::emit_JSOP_LOOPENTRY() { if (!emit_JSOP_JUMPTARGET()) { return false; } frame.syncStack(0); if (!emitWarmUpCounterIncrement(LoopEntryCanIonOsr(pc))) { return false; } if (script->trackRecordReplayProgress()) { masm.inc64(AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter())); } return true; } bool BaselineCompiler::emit_JSOP_VOID() { frame.pop(); frame.push(UndefinedValue()); return true; } bool BaselineCompiler::emit_JSOP_UNDEFINED() { // If this ever changes, change what JSOP_GIMPLICITTHIS does too. frame.push(UndefinedValue()); return true; } bool BaselineCompiler::emit_JSOP_HOLE() { frame.push(MagicValue(JS_ELEMENTS_HOLE)); return true; } bool BaselineCompiler::emit_JSOP_NULL() { frame.push(NullValue()); return true; } typedef bool (*ThrowCheckIsObjectFn)(JSContext*, CheckIsObjectKind); static const VMFunction ThrowCheckIsObjectInfo = FunctionInfo(ThrowCheckIsObject, "ThrowCheckIsObject"); bool BaselineCompiler::emit_JSOP_CHECKISOBJ() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); Label ok; masm.branchTestObject(Assembler::Equal, R0, &ok); prepareVMCall(); pushArg(Imm32(GET_UINT8(pc))); if (!callVM(ThrowCheckIsObjectInfo)) { return false; } masm.bind(&ok); return true; } typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); static const VMFunction CheckIsCallableInfo = FunctionInfo(CheckIsCallable, "CheckIsCallable"); bool BaselineCompiler::emit_JSOP_CHECKISCALLABLE() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(Imm32(GET_UINT8(pc))); pushArg(R0); if (!callVM(CheckIsCallableInfo)) { return false; } return true; } typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); static const VMFunction ThrowUninitializedThisInfo = FunctionInfo(BaselineThrowUninitializedThis, "BaselineThrowUninitializedThis"); typedef bool (*ThrowInitializedThisFn)(JSContext*); static const VMFunction ThrowInitializedThisInfo = FunctionInfo(BaselineThrowInitializedThis, "BaselineThrowInitializedThis"); bool BaselineCompiler::emit_JSOP_CHECKTHIS() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); return emitCheckThis(R0); } bool BaselineCompiler::emit_JSOP_CHECKTHISREINIT() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); return emitCheckThis(R0, /* reinit = */true); } bool BaselineCompiler::emitCheckThis(ValueOperand val, bool reinit) { Label thisOK; if (reinit) { masm.branchTestMagic(Assembler::Equal, val, &thisOK); } else { masm.branchTestMagic(Assembler::NotEqual, val, &thisOK); } prepareVMCall(); if (reinit) { if (!callVM(ThrowInitializedThisInfo)) { return false; } } else { masm.loadBaselineFramePtr(BaselineFrameReg, val.scratchReg()); pushArg(val.scratchReg()); if (!callVM(ThrowUninitializedThisInfo)) { return false; } } masm.bind(&thisOK); return true; } typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue); static const VMFunction ThrowBadDerivedReturnInfo = FunctionInfo(jit::ThrowBadDerivedReturn, "ThrowBadDerivedReturn"); bool BaselineCompiler::emit_JSOP_CHECKRETURN() { MOZ_ASSERT(script->isDerivedClassConstructor()); // Load |this| in R0, return value in R1. frame.popRegsAndSync(1); emitLoadReturnValue(R1); Label done, returnOK; masm.branchTestObject(Assembler::Equal, R1, &done); masm.branchTestUndefined(Assembler::Equal, R1, &returnOK); prepareVMCall(); pushArg(R1); if (!callVM(ThrowBadDerivedReturnInfo)) { return false; } masm.assumeUnreachable("Should throw on bad derived constructor return"); masm.bind(&returnOK); if (!emitCheckThis(R0)) { return false; } // Store |this| in the return value slot. masm.storeValue(R0, frame.addressOfReturnValue()); masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); masm.bind(&done); return true; } typedef bool (*GetFunctionThisFn)(JSContext*, BaselineFrame*, MutableHandleValue); static const VMFunction GetFunctionThisInfo = FunctionInfo(jit::BaselineGetFunctionThis, "BaselineGetFunctionThis"); bool BaselineCompiler::emit_JSOP_FUNCTIONTHIS() { MOZ_ASSERT(function()); MOZ_ASSERT(!function()->isArrow()); frame.pushThis(); // In strict mode code or self-hosted functions, |this| is left alone. if (script->strict() || (function() && function()->isSelfHostedBuiltin())) { return true; } // Load |thisv| in R0. Skip the call if it's already an object. Label skipCall; frame.popRegsAndSync(1); masm.branchTestObject(Assembler::Equal, R0, &skipCall); prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); pushArg(R1.scratchReg()); if (!callVM(GetFunctionThisInfo)) { return false; } masm.bind(&skipCall); frame.push(R0); return true; } typedef void (*GetNonSyntacticGlobalThisFn)(JSContext*, HandleObject, MutableHandleValue); static const VMFunction GetNonSyntacticGlobalThisInfo = FunctionInfo(js::GetNonSyntacticGlobalThis, "GetNonSyntacticGlobalThis"); bool BaselineCompiler::emit_JSOP_GLOBALTHIS() { frame.syncStack(0); if (!script->hasNonSyntacticScope()) { LexicalEnvironmentObject* globalLexical = &script->global().lexicalEnvironment(); masm.moveValue(globalLexical->thisValue(), R0); frame.push(R0); return true; } prepareVMCall(); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVM(GetNonSyntacticGlobalThisInfo)) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_TRUE() { frame.push(BooleanValue(true)); return true; } bool BaselineCompiler::emit_JSOP_FALSE() { frame.push(BooleanValue(false)); return true; } bool BaselineCompiler::emit_JSOP_ZERO() { frame.push(Int32Value(0)); return true; } bool BaselineCompiler::emit_JSOP_ONE() { frame.push(Int32Value(1)); return true; } bool BaselineCompiler::emit_JSOP_INT8() { frame.push(Int32Value(GET_INT8(pc))); return true; } bool BaselineCompiler::emit_JSOP_INT32() { frame.push(Int32Value(GET_INT32(pc))); return true; } bool BaselineCompiler::emit_JSOP_UINT16() { frame.push(Int32Value(GET_UINT16(pc))); return true; } bool BaselineCompiler::emit_JSOP_UINT24() { frame.push(Int32Value(GET_UINT24(pc))); return true; } bool +BaselineCompiler::emit_JSOP_RESUMEINDEX() +{ + return emit_JSOP_UINT24(); +} + +bool BaselineCompiler::emit_JSOP_DOUBLE() { frame.push(script->getConst(GET_UINT32_INDEX(pc))); return true; } bool BaselineCompiler::emit_JSOP_STRING() { frame.push(StringValue(script->getAtom(pc))); return true; } bool BaselineCompiler::emit_JSOP_SYMBOL() { unsigned which = GET_UINT8(pc); JS::Symbol* sym = cx->runtime()->wellKnownSymbols->get(which); frame.push(SymbolValue(sym)); return true; } typedef JSObject* (*DeepCloneObjectLiteralFn)(JSContext*, HandleObject, NewObjectKind); static const VMFunction DeepCloneObjectLiteralInfo = FunctionInfo(DeepCloneObjectLiteral, "DeepCloneObjectLiteral"); bool BaselineCompiler::emit_JSOP_OBJECT() { if (cx->realm()->creationOptions().cloneSingletons()) { RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc))); if (!obj) { return false; } prepareVMCall(); pushArg(ImmWord(TenuredObject)); pushArg(ImmGCPtr(obj)); if (!callVM(DeepCloneObjectLiteralInfo)) { return false; } // Box and push return value. masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } cx->realm()->behaviors().setSingletonsAsValues(); frame.push(ObjectValue(*script->getObject(pc))); return true; } bool BaselineCompiler::emit_JSOP_CALLSITEOBJ() { RootedObject cso(cx, script->getObject(pc)); RootedObject raw(cx, script->getObject(GET_UINT32_INDEX(pc) + 1)); if (!cso || !raw) { return false; } if (!ProcessCallSiteObjOperation(cx, cso, raw)) { return false; } frame.push(ObjectValue(*cso)); return true; } typedef JSObject* (*CloneRegExpObjectFn)(JSContext*, Handle); static const VMFunction CloneRegExpObjectInfo = FunctionInfo(CloneRegExpObject, "CloneRegExpObject"); bool BaselineCompiler::emit_JSOP_REGEXP() { RootedObject reObj(cx, script->getRegExp(pc)); prepareVMCall(); pushArg(ImmGCPtr(reObj)); if (!callVM(CloneRegExpObjectInfo)) { return false; } // Box and push return value. masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef JSObject* (*LambdaFn)(JSContext*, HandleFunction, HandleObject); static const VMFunction LambdaInfo = FunctionInfo(js::Lambda, "Lambda"); bool BaselineCompiler::emit_JSOP_LAMBDA() { RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc))); prepareVMCall(); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); pushArg(R0.scratchReg()); pushArg(ImmGCPtr(fun)); if (!callVM(LambdaInfo)) { return false; } // Box and push return value. masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject, HandleValue); static const VMFunction LambdaArrowInfo = FunctionInfo(js::LambdaArrow, "LambdaArrow"); bool BaselineCompiler::emit_JSOP_LAMBDA_ARROW() { // Keep pushed newTarget in R0. frame.popRegsAndSync(1); RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc))); prepareVMCall(); masm.loadPtr(frame.addressOfEnvironmentChain(), R2.scratchReg()); pushArg(R0); pushArg(R2.scratchReg()); pushArg(ImmGCPtr(fun)); if (!callVM(LambdaArrowInfo)) { return false; } // Box and push return value. masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); static const VMFunction SetFunNameInfo = FunctionInfo(js::SetFunctionNameIfNoOwnName, "SetFunName"); bool BaselineCompiler::emit_JSOP_SETFUNNAME() { frame.popRegsAndSync(2); frame.push(R0); frame.syncStack(0); FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(pc)); masm.unboxObject(R0, R0.scratchReg()); prepareVMCall(); pushArg(Imm32(int32_t(prefixKind))); pushArg(R1); pushArg(R0.scratchReg()); return callVM(SetFunNameInfo); } void BaselineCompiler::storeValue(const StackValue* source, const Address& dest, const ValueOperand& scratch) { switch (source->kind()) { case StackValue::Constant: masm.storeValue(source->constant(), dest); break; case StackValue::Register: masm.storeValue(source->reg(), dest); break; case StackValue::LocalSlot: masm.loadValue(frame.addressOfLocal(source->localSlot()), scratch); masm.storeValue(scratch, dest); break; case StackValue::ArgSlot: masm.loadValue(frame.addressOfArg(source->argSlot()), scratch); masm.storeValue(scratch, dest); break; case StackValue::ThisSlot: masm.loadValue(frame.addressOfThis(), scratch); masm.storeValue(scratch, dest); break; case StackValue::EvalNewTargetSlot: MOZ_ASSERT(script->isForEval()); masm.loadValue(frame.addressOfEvalNewTarget(), scratch); masm.storeValue(scratch, dest); break; case StackValue::Stack: masm.loadValue(frame.addressOfStackValue(source), scratch); masm.storeValue(scratch, dest); break; default: MOZ_CRASH("Invalid kind"); } } bool BaselineCompiler::emit_JSOP_BITOR() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_BITXOR() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_BITAND() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_LSH() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_RSH() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_URSH() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_ADD() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_SUB() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_MUL() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_DIV() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_MOD() { return emitBinaryArith(); } bool BaselineCompiler::emit_JSOP_POW() { return emitBinaryArith(); } bool BaselineCompiler::emitBinaryArith() { // Keep top JSStack value in R0 and R2 frame.popRegsAndSync(2); // Call IC ICBinaryArith_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emitUnaryArith() { // Keep top stack value in R0. frame.popRegsAndSync(1); // Call IC ICUnaryArith_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_BITNOT() { return emitUnaryArith(); } bool BaselineCompiler::emit_JSOP_NEG() { return emitUnaryArith(); } bool BaselineCompiler::emit_JSOP_LT() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_LE() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_GT() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_GE() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_EQ() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_NE() { return emitCompare(); } bool BaselineCompiler::emitCompare() { // CODEGEN // Keep top JSStack value in R0 and R1. frame.popRegsAndSync(2); // Call IC. ICCompare_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0, JSVAL_TYPE_BOOLEAN); return true; } bool BaselineCompiler::emit_JSOP_STRICTEQ() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_STRICTNE() { return emitCompare(); } bool BaselineCompiler::emit_JSOP_CONDSWITCH() { return true; } bool BaselineCompiler::emit_JSOP_CASE() { frame.popRegsAndSync(1); Label done; jsbytecode* target = pc + GET_JUMP_OFFSET(pc); masm.branchTestBooleanTruthy(/* branchIfTrue */ false, R0, &done); { // Pop the switch value if the case matches. masm.addToStackPtr(Imm32(sizeof(Value))); masm.jump(labelOf(target)); } masm.bind(&done); return true; } bool BaselineCompiler::emit_JSOP_DEFAULT() { frame.pop(); return emit_JSOP_GOTO(); } bool BaselineCompiler::emit_JSOP_LINENO() { return true; } bool BaselineCompiler::emit_JSOP_NEWARRAY() { frame.syncStack(0); uint32_t length = GET_UINT32(pc); MOZ_ASSERT(length <= INT32_MAX, "the bytecode emitter must fail to compile code that would " "produce JSOP_NEWARRAY with a length exceeding int32_t range"); // Pass length in R0. masm.move32(Imm32(AssertedCast(length)), R0.scratchReg()); ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array); if (!group) { return false; } ICNewArray_Fallback::Compiler stubCompiler(cx, group); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } typedef ArrayObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap); const VMFunction jit::NewArrayCopyOnWriteInfo = FunctionInfo(js::NewDenseCopyOnWriteArray, "NewDenseCopyOnWriteArray"); bool BaselineCompiler::emit_JSOP_NEWARRAY_COPYONWRITE() { RootedScript scriptRoot(cx, script); JSObject* obj = ObjectGroup::getOrFixupCopyOnWriteObject(cx, scriptRoot, pc); if (!obj) { return false; } prepareVMCall(); pushArg(Imm32(gc::DefaultHeap)); pushArg(ImmGCPtr(obj)); if (!callVM(NewArrayCopyOnWriteInfo)) { return false; } // Box and push return value. masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_INITELEM_ARRAY() { // Keep the object and rhs on the stack. frame.syncStack(0); // Load object in R0, index in R1. masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); uint32_t index = GET_UINT32(pc); MOZ_ASSERT(index <= INT32_MAX, "the bytecode emitter must fail to compile code that would " "produce JSOP_INITELEM_ARRAY with a length exceeding " "int32_t range"); masm.moveValue(Int32Value(AssertedCast(index)), R1); // Call IC. ICSetElem_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Pop the rhs, so that the object is on the top of the stack. frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_NEWOBJECT() { frame.syncStack(0); ICNewObject_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_NEWINIT() { frame.syncStack(0); ICNewObject_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_INITELEM() { // Store RHS in the scratch slot. storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); frame.pop(); // Keep object and index in R0 and R1. frame.popRegsAndSync(2); // Push the object to store the result of the IC. frame.push(R0); frame.syncStack(0); // Keep RHS on the stack. frame.pushScratchValue(); // Call IC. ICSetElem_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Pop the rhs, so that the object is on the top of the stack. frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_INITHIDDENELEM() { return emit_JSOP_INITELEM(); } typedef bool (*MutateProtoFn)(JSContext* cx, HandlePlainObject obj, HandleValue newProto); static const VMFunction MutateProtoInfo = FunctionInfo(MutatePrototype, "MutatePrototype"); bool BaselineCompiler::emit_JSOP_MUTATEPROTO() { // Keep values on the stack for the decompiler. frame.syncStack(0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-2)), R0.scratchReg()); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); prepareVMCall(); pushArg(R1); pushArg(R0.scratchReg()); if (!callVM(MutateProtoInfo)) { return false; } frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_INITPROP() { // Load lhs in R0, rhs in R1. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); // Call IC. ICSetProp_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } // Leave the object on the stack. frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_INITLOCKEDPROP() { return emit_JSOP_INITPROP(); } bool BaselineCompiler::emit_JSOP_INITHIDDENPROP() { return emit_JSOP_INITPROP(); } bool BaselineCompiler::emit_JSOP_GETELEM() { // Keep top two stack values in R0 and R1. frame.popRegsAndSync(2); // Call IC. ICGetElem_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_GETELEM_SUPER() { // Store obj in the scratch slot. storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); frame.pop(); // Keep receiver and index in R0 and R1. frame.popRegsAndSync(2); // Keep obj on the stack. frame.pushScratchValue(); ICGetElem_Fallback::Compiler stubCompiler(cx, /* hasReceiver = */ true); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.pop(); // This value is also popped in InitFromBailout. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_CALLELEM() { return emit_JSOP_GETELEM(); } bool BaselineCompiler::emit_JSOP_SETELEM() { // Store RHS in the scratch slot. storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); frame.pop(); // Keep object and index in R0 and R1. frame.popRegsAndSync(2); // Keep RHS on the stack. frame.pushScratchValue(); // Call IC. ICSetElem_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } return true; } bool BaselineCompiler::emit_JSOP_STRICTSETELEM() { return emit_JSOP_SETELEM(); } bool BaselineCompiler::emit_JSOP_SETELEM_SUPER() { bool strict = IsCheckStrictOp(JSOp(*pc)); // Incoming stack is |receiver, propval, obj, rval|. We need to shuffle // stack to leave rval when operation is complete. // Pop rval into R0, then load receiver into R1 and replace with rval. frame.popRegsAndSync(1); masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R1); masm.storeValue(R0, frame.addressOfStackValue(frame.peek(-3))); prepareVMCall(); pushArg(Imm32(strict)); pushArg(R1); // receiver pushArg(R0); // rval masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); pushArg(R0); // propval masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); pushArg(R0.scratchReg()); // obj if (!callVM(SetObjectElementInfo)) { return false; } frame.popn(2); return true; } bool BaselineCompiler::emit_JSOP_STRICTSETELEM_SUPER() { return emit_JSOP_SETELEM_SUPER(); } typedef bool (*DeleteElementFn)(JSContext*, HandleValue, HandleValue, bool*); static const VMFunction DeleteElementStrictInfo = FunctionInfo(DeleteElementJit, "DeleteElementStrict"); static const VMFunction DeleteElementNonStrictInfo = FunctionInfo(DeleteElementJit, "DeleteElementNonStrict"); bool BaselineCompiler::emit_JSOP_DELELEM() { // Keep values on the stack for the decompiler. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); prepareVMCall(); pushArg(R1); pushArg(R0); bool strict = JSOp(*pc) == JSOP_STRICTDELELEM; if (!callVM(strict ? DeleteElementStrictInfo : DeleteElementNonStrictInfo)) { return false; } masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); frame.popn(2); frame.push(R1); return true; } bool BaselineCompiler::emit_JSOP_STRICTDELELEM() { return emit_JSOP_DELELEM(); } bool BaselineCompiler::emit_JSOP_IN() { frame.popRegsAndSync(2); ICIn_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_HASOWN() { frame.popRegsAndSync(2); ICHasOwn_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_GETGNAME() { if (script->hasNonSyntacticScope()) { return emit_JSOP_GETNAME(); } RootedPropertyName name(cx, script->getName(pc)); // These names are non-configurable on the global and cannot be shadowed. if (name == cx->names().undefined) { frame.push(UndefinedValue()); return true; } if (name == cx->names().NaN) { frame.push(cx->runtime()->NaNValue); return true; } if (name == cx->names().Infinity) { frame.push(cx->runtime()->positiveInfinityValue); return true; } frame.syncStack(0); masm.movePtr(ImmGCPtr(&script->global().lexicalEnvironment()), R0.scratchReg()); // Call IC. ICGetName_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_BINDGNAME() { if (!script->hasNonSyntacticScope()) { // We can bind name to the global lexical scope if the binding already // exists, is initialized, and is writable (i.e., an initialized // 'let') at compile time. RootedPropertyName name(cx, script->getName(pc)); Rooted env(cx, &script->global().lexicalEnvironment()); if (Shape* shape = env->lookup(cx, name)) { if (shape->writable() && !env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { frame.push(ObjectValue(*env)); return true; } } else if (Shape* shape = script->global().lookup(cx, name)) { // If the property does not currently exist on the global lexical // scope, we can bind name to the global object if the property // exists on the global and is non-configurable, as then it cannot // be shadowed. if (!shape->configurable()) { frame.push(ObjectValue(script->global())); return true; } } // Otherwise we have to use the environment chain. } return emit_JSOP_BINDNAME(); } typedef JSObject* (*BindVarFn)(JSContext*, HandleObject); static const VMFunction BindVarInfo = FunctionInfo(jit::BindVar, "BindVar"); bool BaselineCompiler::emit_JSOP_BINDVAR() { frame.syncStack(0); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); if (!callVM(BindVarInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_SETPROP() { // Keep lhs in R0, rhs in R1. frame.popRegsAndSync(2); // Keep RHS on the stack. frame.push(R1); frame.syncStack(0); // Call IC. ICSetProp_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } return true; } bool BaselineCompiler::emit_JSOP_STRICTSETPROP() { return emit_JSOP_SETPROP(); } bool BaselineCompiler::emit_JSOP_SETNAME() { return emit_JSOP_SETPROP(); } bool BaselineCompiler::emit_JSOP_STRICTSETNAME() { return emit_JSOP_SETPROP(); } bool BaselineCompiler::emit_JSOP_SETGNAME() { return emit_JSOP_SETPROP(); } bool BaselineCompiler::emit_JSOP_STRICTSETGNAME() { return emit_JSOP_SETPROP(); } typedef bool (*SetPropertySuperFn)(JSContext*, HandleObject, HandleValue, HandlePropertyName, HandleValue, bool); static const VMFunction SetPropertySuperInfo = FunctionInfo(js::SetPropertySuper, "SetPropertySuper"); bool BaselineCompiler::emit_JSOP_SETPROP_SUPER() { bool strict = IsCheckStrictOp(JSOp(*pc)); // Incoming stack is |receiver, obj, rval|. We need to shuffle stack to // leave rval when operation is complete. // Pop rval into R0, then load receiver into R1 and replace with rval. frame.popRegsAndSync(1); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); masm.storeValue(R0, frame.addressOfStackValue(frame.peek(-2))); prepareVMCall(); pushArg(Imm32(strict)); pushArg(R0); // rval pushArg(ImmGCPtr(script->getName(pc))); pushArg(R1); // receiver masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); pushArg(R0.scratchReg()); // obj if (!callVM(SetPropertySuperInfo)) { return false; } frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_STRICTSETPROP_SUPER() { return emit_JSOP_SETPROP_SUPER(); } bool BaselineCompiler::emit_JSOP_GETPROP() { // Keep object in R0. frame.popRegsAndSync(1); // Call IC. ICGetProp_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_CALLPROP() { return emit_JSOP_GETPROP(); } bool BaselineCompiler::emit_JSOP_LENGTH() { return emit_JSOP_GETPROP(); } bool BaselineCompiler::emit_JSOP_GETBOUNDNAME() { return emit_JSOP_GETPROP(); } bool BaselineCompiler::emit_JSOP_GETPROP_SUPER() { // Receiver -> R1, Object -> R0 frame.popRegsAndSync(1); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); frame.pop(); ICGetProp_Fallback::Compiler compiler(cx, /* hasReceiver = */ true); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } typedef bool (*DeletePropertyFn)(JSContext*, HandleValue, HandlePropertyName, bool*); static const VMFunction DeletePropertyStrictInfo = FunctionInfo(DeletePropertyJit, "DeletePropertyStrict"); static const VMFunction DeletePropertyNonStrictInfo = FunctionInfo(DeletePropertyJit, "DeletePropertyNonStrict"); bool BaselineCompiler::emit_JSOP_DELPROP() { // Keep value on the stack for the decompiler. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(ImmGCPtr(script->getName(pc))); pushArg(R0); bool strict = JSOp(*pc) == JSOP_STRICTDELPROP; if (!callVM(strict ? DeletePropertyStrictInfo : DeletePropertyNonStrictInfo)) { return false; } masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); frame.pop(); frame.push(R1); return true; } bool BaselineCompiler::emit_JSOP_STRICTDELPROP() { return emit_JSOP_DELPROP(); } void BaselineCompiler::getEnvironmentCoordinateObject(Register reg) { EnvironmentCoordinate ec(pc); masm.loadPtr(frame.addressOfEnvironmentChain(), reg); for (unsigned i = ec.hops(); i; i--) { masm.unboxObject(Address(reg, EnvironmentObject::offsetOfEnclosingEnvironment()), reg); } } Address BaselineCompiler::getEnvironmentCoordinateAddressFromObject(Register objReg, Register reg) { EnvironmentCoordinate ec(pc); Shape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc); if (shape->numFixedSlots() <= ec.slot()) { masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), reg); return Address(reg, (ec.slot() - shape->numFixedSlots()) * sizeof(Value)); } return Address(objReg, NativeObject::getFixedSlotOffset(ec.slot())); } Address BaselineCompiler::getEnvironmentCoordinateAddress(Register reg) { getEnvironmentCoordinateObject(reg); return getEnvironmentCoordinateAddressFromObject(reg, reg); } bool BaselineCompiler::emit_JSOP_GETALIASEDVAR() { frame.syncStack(0); Address address = getEnvironmentCoordinateAddress(R0.scratchReg()); masm.loadValue(address, R0); if (ionCompileable_) { // No need to monitor types if we know Ion can't compile this script. ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_SETALIASEDVAR() { JSScript* outerScript = EnvironmentCoordinateFunctionScript(script, pc); if (outerScript && outerScript->treatAsRunOnce()) { // Type updates for this operation might need to be tracked, so treat // this as a SETPROP. // Load rhs into R1. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); // Load and box lhs into R0. getEnvironmentCoordinateObject(R2.scratchReg()); masm.tagValue(JSVAL_TYPE_OBJECT, R2.scratchReg(), R0); // Call SETPROP IC. ICSetProp_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } return true; } // Keep rvalue in R0. frame.popRegsAndSync(1); Register objReg = R2.scratchReg(); getEnvironmentCoordinateObject(objReg); Address address = getEnvironmentCoordinateAddressFromObject(objReg, R1.scratchReg()); masm.guardedCallPreBarrier(address, MIRType::Value); masm.storeValue(R0, address); frame.push(R0); // Only R0 is live at this point. // Scope coordinate object is already in R2.scratchReg(). Register temp = R1.scratchReg(); Label skipBarrier; masm.branchPtrInNurseryChunk(Assembler::Equal, objReg, temp, &skipBarrier); masm.branchValueIsNurseryCell(Assembler::NotEqual, R0, temp, &skipBarrier); masm.call(&postBarrierSlot_); // Won't clobber R0 masm.bind(&skipBarrier); return true; } bool BaselineCompiler::emit_JSOP_GETNAME() { frame.syncStack(0); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); // Call IC. ICGetName_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_BINDNAME() { frame.syncStack(0); if (*pc == JSOP_BINDGNAME && !script->hasNonSyntacticScope()) { masm.movePtr(ImmGCPtr(&script->global().lexicalEnvironment()), R0.scratchReg()); } else { masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); } // Call IC. ICBindName_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } typedef bool (*DeleteNameFn)(JSContext*, HandlePropertyName, HandleObject, MutableHandleValue); static const VMFunction DeleteNameInfo = FunctionInfo(DeleteNameOperation, "DeleteNameOperation"); bool BaselineCompiler::emit_JSOP_DELNAME() { frame.syncStack(0); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(ImmGCPtr(script->getName(pc))); if (!callVM(DeleteNameInfo)) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_GETIMPORT() { ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script); MOZ_ASSERT(env); ModuleEnvironmentObject* targetEnv; Shape* shape; MOZ_ALWAYS_TRUE(env->lookupImport(NameToId(script->getName(pc)), &targetEnv, &shape)); EnsureTrackPropertyTypes(cx, targetEnv, shape->propid()); frame.syncStack(0); uint32_t slot = shape->slot(); Register scratch = R0.scratchReg(); masm.movePtr(ImmGCPtr(targetEnv), scratch); if (slot < targetEnv->numFixedSlots()) { masm.loadValue(Address(scratch, NativeObject::getFixedSlotOffset(slot)), R0); } else { masm.loadPtr(Address(scratch, NativeObject::offsetOfSlots()), scratch); masm.loadValue(Address(scratch, (slot - targetEnv->numFixedSlots()) * sizeof(Value)), R0); } // Imports are initialized by this point except in rare circumstances, so // don't emit a check unless we have to. if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { if (!emitUninitializedLexicalCheck(R0)) { return false; } } if (ionCompileable_) { // No need to monitor types if we know Ion can't compile this script. ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_GETINTRINSIC() { frame.syncStack(0); ICGetIntrinsic_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } typedef bool (*DefVarFn)(JSContext*, HandlePropertyName, unsigned, HandleObject); static const VMFunction DefVarInfo = FunctionInfo(DefVar, "DefVar"); bool BaselineCompiler::emit_JSOP_DEFVAR() { frame.syncStack(0); unsigned attrs = JSPROP_ENUMERATE; if (!script->isForEval()) { attrs |= JSPROP_PERMANENT; } MOZ_ASSERT(attrs <= UINT32_MAX); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(Imm32(attrs)); pushArg(ImmGCPtr(script->getName(pc))); return callVM(DefVarInfo); } typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned, HandleObject); static const VMFunction DefLexicalInfo = FunctionInfo(DefLexical, "DefLexical"); bool BaselineCompiler::emit_JSOP_DEFCONST() { return emit_JSOP_DEFLET(); } bool BaselineCompiler::emit_JSOP_DEFLET() { frame.syncStack(0); unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; if (*pc == JSOP_DEFCONST) { attrs |= JSPROP_READONLY; } MOZ_ASSERT(attrs <= UINT32_MAX); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(Imm32(attrs)); pushArg(ImmGCPtr(script->getName(pc))); return callVM(DefLexicalInfo); } typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject, HandleFunction); static const VMFunction DefFunOperationInfo = FunctionInfo(DefFunOperation, "DefFunOperation"); bool BaselineCompiler::emit_JSOP_DEFFUN() { frame.popRegsAndSync(1); masm.unboxObject(R0, R0.scratchReg()); masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(R1.scratchReg()); pushArg(ImmGCPtr(script)); return callVM(DefFunOperationInfo); } typedef bool (*InitPropGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandlePropertyName, HandleObject); static const VMFunction InitPropGetterSetterInfo = FunctionInfo(InitGetterSetterOperation, "InitPropGetterSetterOperation"); bool BaselineCompiler::emitInitPropGetterSetter() { MOZ_ASSERT(JSOp(*pc) == JSOP_INITPROP_GETTER || JSOp(*pc) == JSOP_INITHIDDENPROP_GETTER || JSOp(*pc) == JSOP_INITPROP_SETTER || JSOp(*pc) == JSOP_INITHIDDENPROP_SETTER); // Keep values on the stack for the decompiler. frame.syncStack(0); prepareVMCall(); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); masm.unboxObject(frame.addressOfStackValue(frame.peek(-2)), R1.scratchReg()); pushArg(R0.scratchReg()); pushArg(ImmGCPtr(script->getName(pc))); pushArg(R1.scratchReg()); pushArg(ImmPtr(pc)); if (!callVM(InitPropGetterSetterInfo)) { return false; } frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_INITPROP_GETTER() { return emitInitPropGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITHIDDENPROP_GETTER() { return emitInitPropGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITPROP_SETTER() { return emitInitPropGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITHIDDENPROP_SETTER() { return emitInitPropGetterSetter(); } typedef bool (*InitElemGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandleValue, HandleObject); static const VMFunction InitElemGetterSetterInfo = FunctionInfo(InitGetterSetterOperation, "InitElemGetterSetterOperation"); bool BaselineCompiler::emitInitElemGetterSetter() { MOZ_ASSERT(JSOp(*pc) == JSOP_INITELEM_GETTER || JSOp(*pc) == JSOP_INITHIDDENELEM_GETTER || JSOp(*pc) == JSOP_INITELEM_SETTER || JSOp(*pc) == JSOP_INITHIDDENELEM_SETTER); // Load index and value in R0 and R1, but keep values on the stack for the // decompiler. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R1.scratchReg()); prepareVMCall(); pushArg(R1.scratchReg()); pushArg(R0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg()); pushArg(R0.scratchReg()); pushArg(ImmPtr(pc)); if (!callVM(InitElemGetterSetterInfo)) { return false; } frame.popn(2); return true; } bool BaselineCompiler::emit_JSOP_INITELEM_GETTER() { return emitInitElemGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITHIDDENELEM_GETTER() { return emitInitElemGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITELEM_SETTER() { return emitInitElemGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITHIDDENELEM_SETTER() { return emitInitElemGetterSetter(); } bool BaselineCompiler::emit_JSOP_INITELEM_INC() { // Keep the object and rhs on the stack. frame.syncStack(0); // Load object in R0, index in R1. masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R0); masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); // Call IC. ICSetElem_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Pop the rhs frame.pop(); // Increment index Address indexAddr = frame.addressOfStackValue(frame.peek(-1)); #ifdef DEBUG Label isInt32; masm.branchTestInt32(Assembler::Equal, indexAddr, &isInt32); masm.assumeUnreachable("INITELEM_INC index must be Int32"); masm.bind(&isInt32); #endif masm.incrementInt32Value(indexAddr); return true; } bool BaselineCompiler::emit_JSOP_GETLOCAL() { frame.pushLocal(GET_LOCALNO(pc)); return true; } bool BaselineCompiler::emit_JSOP_SETLOCAL() { // Ensure no other StackValue refers to the old value, for instance i + (i = 3). // This also allows us to use R0 as scratch below. frame.syncStack(1); uint32_t local = GET_LOCALNO(pc); storeValue(frame.peek(-1), frame.addressOfLocal(local), R0); return true; } bool BaselineCompiler::emitFormalArgAccess(uint32_t arg, bool get) { // Fast path: the script does not use |arguments| or formals don't // alias the arguments object. if (!script->argumentsAliasesFormals()) { if (get) { frame.pushArg(arg); } else { // See the comment in emit_JSOP_SETLOCAL. frame.syncStack(1); storeValue(frame.peek(-1), frame.addressOfArg(arg), R0); } return true; } // Sync so that we can use R0. frame.syncStack(0); // If the script is known to have an arguments object, we can just use it. // Else, we *may* have an arguments object (because we can't invalidate // when needsArgsObj becomes |true|), so we have to test HAS_ARGS_OBJ. Label done; if (!script->needsArgsObj()) { Label hasArgsObj; masm.branchTest32(Assembler::NonZero, frame.addressOfFlags(), Imm32(BaselineFrame::HAS_ARGS_OBJ), &hasArgsObj); if (get) { masm.loadValue(frame.addressOfArg(arg), R0); } else { storeValue(frame.peek(-1), frame.addressOfArg(arg), R0); } masm.jump(&done); masm.bind(&hasArgsObj); } // Load the arguments object data vector. Register reg = R2.scratchReg(); masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg); masm.loadPrivate(Address(reg, ArgumentsObject::getDataSlotOffset()), reg); // Load/store the argument. Address argAddr(reg, ArgumentsData::offsetOfArgs() + arg * sizeof(Value)); if (get) { masm.loadValue(argAddr, R0); frame.push(R0); } else { masm.guardedCallPreBarrier(argAddr, MIRType::Value); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); masm.storeValue(R0, argAddr); MOZ_ASSERT(frame.numUnsyncedSlots() == 0); Register temp = R1.scratchReg(); // Reload the arguments object Register reg = R2.scratchReg(); masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg); Label skipBarrier; masm.branchPtrInNurseryChunk(Assembler::Equal, reg, temp, &skipBarrier); masm.branchValueIsNurseryCell(Assembler::NotEqual, R0, temp, &skipBarrier); masm.call(&postBarrierSlot_); masm.bind(&skipBarrier); } masm.bind(&done); return true; } bool BaselineCompiler::emit_JSOP_GETARG() { uint32_t arg = GET_ARGNO(pc); return emitFormalArgAccess(arg, /* get = */ true); } bool BaselineCompiler::emit_JSOP_SETARG() { // Ionmonkey can't inline functions with SETARG with magic arguments. if (!script->argsObjAliasesFormals() && script->argumentsAliasesFormals()) { script->setUninlineable(); } modifiesArguments_ = true; uint32_t arg = GET_ARGNO(pc); return emitFormalArgAccess(arg, /* get = */ false); } bool BaselineCompiler::emit_JSOP_NEWTARGET() { if (script->isForEval()) { frame.pushEvalNewTarget(); return true; } MOZ_ASSERT(function()); frame.syncStack(0); if (function()->isArrow()) { // Arrow functions store their |new.target| value in an // extended slot. Register scratch = R0.scratchReg(); masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch); masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowNewTargetSlot()), R0); frame.push(R0); return true; } // if (isConstructing()) push(argv[Max(numActualArgs, numFormalArgs)]) Label notConstructing, done; masm.branchTestPtr(Assembler::Zero, frame.addressOfCalleeToken(), Imm32(CalleeToken_FunctionConstructing), ¬Constructing); Register argvLen = R0.scratchReg(); Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); masm.loadPtr(actualArgs, argvLen); Label useNFormals; masm.branchPtr(Assembler::Below, argvLen, Imm32(function()->nargs()), &useNFormals); { BaseValueIndex newTarget(BaselineFrameReg, argvLen, BaselineFrame::offsetOfArg(0)); masm.loadValue(newTarget, R0); masm.jump(&done); } masm.bind(&useNFormals); { Address newTarget(BaselineFrameReg, BaselineFrame::offsetOfArg(0) + (function()->nargs() * sizeof(Value))); masm.loadValue(newTarget, R0); masm.jump(&done); } // else push(undefined) masm.bind(¬Constructing); masm.moveValue(UndefinedValue(), R0); masm.bind(&done); frame.push(R0); return true; } typedef bool (*ThrowRuntimeLexicalErrorFn)(JSContext* cx, unsigned); static const VMFunction ThrowRuntimeLexicalErrorInfo = FunctionInfo(jit::ThrowRuntimeLexicalError, "ThrowRuntimeLexicalError"); bool BaselineCompiler::emitThrowConstAssignment() { prepareVMCall(); pushArg(Imm32(JSMSG_BAD_CONST_ASSIGN)); return callVM(ThrowRuntimeLexicalErrorInfo); } bool BaselineCompiler::emit_JSOP_THROWSETCONST() { return emitThrowConstAssignment(); } bool BaselineCompiler::emit_JSOP_THROWSETALIASEDCONST() { return emitThrowConstAssignment(); } bool BaselineCompiler::emit_JSOP_THROWSETCALLEE() { return emitThrowConstAssignment(); } bool BaselineCompiler::emitUninitializedLexicalCheck(const ValueOperand& val) { Label done; masm.branchTestMagicValue(Assembler::NotEqual, val, JS_UNINITIALIZED_LEXICAL, &done); prepareVMCall(); pushArg(Imm32(JSMSG_UNINITIALIZED_LEXICAL)); if (!callVM(ThrowRuntimeLexicalErrorInfo)) { return false; } masm.bind(&done); return true; } bool BaselineCompiler::emit_JSOP_CHECKLEXICAL() { frame.syncStack(0); masm.loadValue(frame.addressOfLocal(GET_LOCALNO(pc)), R0); return emitUninitializedLexicalCheck(R0); } bool BaselineCompiler::emit_JSOP_INITLEXICAL() { return emit_JSOP_SETLOCAL(); } bool BaselineCompiler::emit_JSOP_INITGLEXICAL() { frame.popRegsAndSync(1); frame.push(ObjectValue(script->global().lexicalEnvironment())); frame.push(R0); return emit_JSOP_SETPROP(); } bool BaselineCompiler::emit_JSOP_CHECKALIASEDLEXICAL() { frame.syncStack(0); masm.loadValue(getEnvironmentCoordinateAddress(R0.scratchReg()), R0); return emitUninitializedLexicalCheck(R0); } bool BaselineCompiler::emit_JSOP_INITALIASEDLEXICAL() { return emit_JSOP_SETALIASEDVAR(); } bool BaselineCompiler::emit_JSOP_UNINITIALIZED() { frame.push(MagicValue(JS_UNINITIALIZED_LEXICAL)); return true; } bool BaselineCompiler::emitCall() { MOZ_ASSERT(IsCallPC(pc)); bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL; uint32_t argc = GET_ARGC(pc); frame.syncStack(0); masm.move32(Imm32(argc), R0.scratchReg()); // Call IC ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ false); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Update FrameInfo. frame.popn(2 + argc + construct); frame.push(R0); return true; } bool BaselineCompiler::emitSpreadCall() { MOZ_ASSERT(IsCallPC(pc)); frame.syncStack(0); masm.move32(Imm32(1), R0.scratchReg()); // Call IC bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL; ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ true); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } // Update FrameInfo. frame.popn(3 + construct); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_CALL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_CALL_IGNORES_RV() { return emitCall(); } bool BaselineCompiler::emit_JSOP_CALLITER() { return emitCall(); } bool BaselineCompiler::emit_JSOP_NEW() { return emitCall(); } bool BaselineCompiler::emit_JSOP_SUPERCALL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_FUNCALL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_FUNAPPLY() { return emitCall(); } bool BaselineCompiler::emit_JSOP_EVAL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_STRICTEVAL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_SPREADCALL() { return emitSpreadCall(); } bool BaselineCompiler::emit_JSOP_SPREADNEW() { return emitSpreadCall(); } bool BaselineCompiler::emit_JSOP_SPREADSUPERCALL() { return emitSpreadCall(); } bool BaselineCompiler::emit_JSOP_SPREADEVAL() { return emitSpreadCall(); } bool BaselineCompiler::emit_JSOP_STRICTSPREADEVAL() { return emitSpreadCall(); } typedef bool (*OptimizeSpreadCallFn)(JSContext*, HandleValue, bool*); static const VMFunction OptimizeSpreadCallInfo = FunctionInfo(OptimizeSpreadCall, "OptimizeSpreadCall"); bool BaselineCompiler::emit_JSOP_OPTIMIZE_SPREADCALL() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(R0); if (!callVM(OptimizeSpreadCallInfo)) { return false; } masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R0); frame.push(R0); return true; } typedef bool (*ImplicitThisFn)(JSContext*, HandleObject, HandlePropertyName, MutableHandleValue); const VMFunction jit::ImplicitThisInfo = FunctionInfo(ImplicitThisOperation, "ImplicitThisOperation"); bool BaselineCompiler::emit_JSOP_IMPLICITTHIS() { frame.syncStack(0); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); prepareVMCall(); pushArg(ImmGCPtr(script->getName(pc))); pushArg(R0.scratchReg()); if (!callVM(ImplicitThisInfo)) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_GIMPLICITTHIS() { if (!script->hasNonSyntacticScope()) { frame.push(UndefinedValue()); return true; } return emit_JSOP_IMPLICITTHIS(); } bool BaselineCompiler::emit_JSOP_INSTANCEOF() { frame.popRegsAndSync(2); ICInstanceOf_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_TYPEOF() { frame.popRegsAndSync(1); ICTypeOf_Fallback::Compiler stubCompiler(cx); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_TYPEOFEXPR() { return emit_JSOP_TYPEOF(); } typedef bool (*ThrowMsgFn)(JSContext*, const unsigned); static const VMFunction ThrowMsgInfo = FunctionInfo(js::ThrowMsgOperation, "ThrowMsgOperation"); bool BaselineCompiler::emit_JSOP_THROWMSG() { prepareVMCall(); pushArg(Imm32(GET_UINT16(pc))); return callVM(ThrowMsgInfo); } typedef bool (*ThrowFn)(JSContext*, HandleValue); static const VMFunction ThrowInfo = FunctionInfo(js::Throw, "Throw"); bool BaselineCompiler::emit_JSOP_THROW() { // Keep value to throw in R0. frame.popRegsAndSync(1); prepareVMCall(); pushArg(R0); return callVM(ThrowInfo); } typedef bool (*ThrowingFn)(JSContext*, HandleValue); static const VMFunction ThrowingInfo = FunctionInfo(js::ThrowingOperation, "ThrowingOperation"); bool BaselineCompiler::emit_JSOP_THROWING() { // Keep value to throw in R0. frame.popRegsAndSync(1); prepareVMCall(); pushArg(R0); return callVM(ThrowingInfo); } bool BaselineCompiler::emit_JSOP_TRY() { if (!emit_JSOP_JUMPTARGET()) { return false; } // Ionmonkey can't inline function with JSOP_TRY. script->setUninlineable(); return true; } bool BaselineCompiler::emit_JSOP_FINALLY() { // JSOP_FINALLY has a def count of 2, but these values are already on the // stack (they're pushed by JSOP_GOSUB). Update the compiler's stack state. frame.setStackDepth(frame.stackDepth() + 2); // To match the interpreter, emit an interrupt check at the start of the // finally block. return emitInterruptCheck(); } bool BaselineCompiler::emit_JSOP_GOSUB() { - // Push |false| so that RETSUB knows the value on top of the - // stack is not an exception but the offset to the op following - // this GOSUB. - frame.push(BooleanValue(false)); - - int32_t nextOffset = script->pcToOffset(GetNextPc(pc)); - frame.push(Int32Value(nextOffset)); - // Jump to the finally block. frame.syncStack(0); jsbytecode* target = pc + GET_JUMP_OFFSET(pc); masm.jump(labelOf(target)); return true; } bool BaselineCompiler::emit_JSOP_RETSUB() { frame.popRegsAndSync(2); - ICRetSub_Fallback::Compiler stubCompiler(cx); - return emitOpIC(stubCompiler.getStub(&stubSpace_)); + Label isReturn; + masm.branchTestBooleanTruthy(/* branchIfTrue = */ false, R0, &isReturn); + + // R0 is |true|. We need to throw R1. + prepareVMCall(); + pushArg(R1); + if (!callVM(ThrowInfo)) { + return false; + } + + masm.bind(&isReturn); + + // R0 is |false|. R1 contains the resumeIndex to jump to. + Register scratch1 = R2.scratchReg(); + Register scratch2 = R0.scratchReg(); + masm.movePtr(ImmGCPtr(script), scratch1); + masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1); + masm.load32(Address(scratch1, BaselineScript::offsetOfResumeEntriesOffset()), scratch2); + masm.addPtr(scratch2, scratch1); + masm.unboxInt32(R1, scratch2); + masm.loadPtr(BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))), scratch1); + masm.jump(scratch1); + return true; } typedef bool (*PushLexicalEnvFn)(JSContext*, BaselineFrame*, Handle); static const VMFunction PushLexicalEnvInfo = FunctionInfo(jit::PushLexicalEnv, "PushLexicalEnv"); bool BaselineCompiler::emit_JSOP_PUSHLEXICALENV() { LexicalScope& scope = script->getScope(pc)->as(); // Call a stub to push the block on the block chain. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(ImmGCPtr(&scope)); pushArg(R0.scratchReg()); return callVM(PushLexicalEnvInfo); } typedef bool (*PopLexicalEnvFn)(JSContext*, BaselineFrame*); static const VMFunction PopLexicalEnvInfo = FunctionInfo(jit::PopLexicalEnv, "PopLexicalEnv"); typedef bool (*DebugLeaveThenPopLexicalEnvFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugLeaveThenPopLexicalEnvInfo = FunctionInfo(jit::DebugLeaveThenPopLexicalEnv, "DebugLeaveThenPopLexicalEnv"); bool BaselineCompiler::emit_JSOP_POPLEXICALENV() { prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); if (compileDebugInstrumentation_) { pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); return callVM(DebugLeaveThenPopLexicalEnvInfo); } pushArg(R0.scratchReg()); return callVM(PopLexicalEnvInfo); } typedef bool (*FreshenLexicalEnvFn)(JSContext*, BaselineFrame*); static const VMFunction FreshenLexicalEnvInfo = FunctionInfo(jit::FreshenLexicalEnv, "FreshenLexicalEnv"); typedef bool (*DebugLeaveThenFreshenLexicalEnvFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugLeaveThenFreshenLexicalEnvInfo = FunctionInfo(jit::DebugLeaveThenFreshenLexicalEnv, "DebugLeaveThenFreshenLexicalEnv"); bool BaselineCompiler::emit_JSOP_FRESHENLEXICALENV() { prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); if (compileDebugInstrumentation_) { pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); return callVM(DebugLeaveThenFreshenLexicalEnvInfo); } pushArg(R0.scratchReg()); return callVM(FreshenLexicalEnvInfo); } typedef bool (*RecreateLexicalEnvFn)(JSContext*, BaselineFrame*); static const VMFunction RecreateLexicalEnvInfo = FunctionInfo(jit::RecreateLexicalEnv, "RecreateLexicalEnv"); typedef bool (*DebugLeaveThenRecreateLexicalEnvFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugLeaveThenRecreateLexicalEnvInfo = FunctionInfo(jit::DebugLeaveThenRecreateLexicalEnv, "DebugLeaveThenRecreateLexicalEnv"); bool BaselineCompiler::emit_JSOP_RECREATELEXICALENV() { prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); if (compileDebugInstrumentation_) { pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); return callVM(DebugLeaveThenRecreateLexicalEnvInfo); } pushArg(R0.scratchReg()); return callVM(RecreateLexicalEnvInfo); } typedef bool (*DebugLeaveLexicalEnvFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugLeaveLexicalEnvInfo = FunctionInfo(jit::DebugLeaveLexicalEnv, "DebugLeaveLexicalEnv"); bool BaselineCompiler::emit_JSOP_DEBUGLEAVELEXICALENV() { if (!compileDebugInstrumentation_) { return true; } prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); return callVM(DebugLeaveLexicalEnvInfo); } typedef bool (*PushVarEnvFn)(JSContext*, BaselineFrame*, HandleScope); static const VMFunction PushVarEnvInfo = FunctionInfo(jit::PushVarEnv, "PushVarEnv"); bool BaselineCompiler::emit_JSOP_PUSHVARENV() { prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(ImmGCPtr(script->getScope(pc))); pushArg(R0.scratchReg()); return callVM(PushVarEnvInfo); } typedef bool (*PopVarEnvFn)(JSContext*, BaselineFrame*); static const VMFunction PopVarEnvInfo = FunctionInfo(jit::PopVarEnv, "PopVarEnv"); bool BaselineCompiler::emit_JSOP_POPVARENV() { prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); return callVM(PopVarEnvInfo); } typedef bool (*EnterWithFn)(JSContext*, BaselineFrame*, HandleValue, Handle); static const VMFunction EnterWithInfo = FunctionInfo(jit::EnterWith, "EnterWith"); bool BaselineCompiler::emit_JSOP_ENTERWITH() { WithScope& withScope = script->getScope(pc)->as(); // Pop "with" object to R0. frame.popRegsAndSync(1); // Call a stub to push the object onto the environment chain. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); pushArg(ImmGCPtr(&withScope)); pushArg(R0); pushArg(R1.scratchReg()); return callVM(EnterWithInfo); } typedef bool (*LeaveWithFn)(JSContext*, BaselineFrame*); static const VMFunction LeaveWithInfo = FunctionInfo(jit::LeaveWith, "LeaveWith"); bool BaselineCompiler::emit_JSOP_LEAVEWITH() { // Call a stub to pop the with object from the environment chain. prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); return callVM(LeaveWithInfo); } typedef bool (*GetAndClearExceptionFn)(JSContext*, MutableHandleValue); static const VMFunction GetAndClearExceptionInfo = FunctionInfo(GetAndClearException, "GetAndClearException"); bool BaselineCompiler::emit_JSOP_EXCEPTION() { prepareVMCall(); if (!callVM(GetAndClearExceptionInfo)) { return false; } frame.push(R0); return true; } typedef bool (*OnDebuggerStatementFn)(JSContext*, BaselineFrame*, jsbytecode* pc, bool*); static const VMFunction OnDebuggerStatementInfo = FunctionInfo(jit::OnDebuggerStatement, "OnDebuggerStatement"); bool BaselineCompiler::emit_JSOP_DEBUGGER() { prepareVMCall(); pushArg(ImmPtr(pc)); frame.assertSyncedStack(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVM(OnDebuggerStatementInfo)) { return false; } // If the stub returns |true|, return the frame's return value. Label done; masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); { masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); masm.jump(&return_); } masm.bind(&done); return true; } typedef bool (*DebugEpilogueFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugEpilogueInfo = FunctionInfo(jit::DebugEpilogueOnBaselineReturn, "DebugEpilogueOnBaselineReturn"); bool BaselineCompiler::emitReturn() { if (compileDebugInstrumentation_) { // Move return value into the frame's rval slot. masm.storeValue(JSReturnOperand, frame.addressOfReturnValue()); masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); // Load BaselineFrame pointer in R0. frame.syncStack(0); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); prepareVMCall(); pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); if (!callVM(DebugEpilogueInfo)) { return false; } // Fix up the RetAddrEntry appended by callVM for on-stack recompilation. retAddrEntries_.back().setKind(RetAddrEntry::Kind::DebugEpilogue); masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); } // Only emit the jump if this JSOP_RETRVAL is not the last instruction. // Not needed for last instruction, because last instruction flows // into return label. if (pc + GetBytecodeLength(pc) < script->codeEnd()) { masm.jump(&return_); } return true; } bool BaselineCompiler::emit_JSOP_RETURN() { MOZ_ASSERT(frame.stackDepth() == 1); frame.popValue(JSReturnOperand); return emitReturn(); } void BaselineCompiler::emitLoadReturnValue(ValueOperand val) { Label done, noRval; masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), Imm32(BaselineFrame::HAS_RVAL), &noRval); masm.loadValue(frame.addressOfReturnValue(), val); masm.jump(&done); masm.bind(&noRval); masm.moveValue(UndefinedValue(), val); masm.bind(&done); } bool BaselineCompiler::emit_JSOP_RETRVAL() { MOZ_ASSERT(frame.stackDepth() == 0); masm.moveValue(UndefinedValue(), JSReturnOperand); if (!script->noScriptRval()) { // Return the value in the return value slot, if any. Label done; Address flags = frame.addressOfFlags(); masm.branchTest32(Assembler::Zero, flags, Imm32(BaselineFrame::HAS_RVAL), &done); masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); masm.bind(&done); } return emitReturn(); } typedef bool (*ToIdFn)(JSContext*, HandleValue, MutableHandleValue); static const VMFunction ToIdInfo = FunctionInfo(js::ToIdOperation, "ToIdOperation"); bool BaselineCompiler::emit_JSOP_TOID() { // Load index in R0, but keep values on the stack for the decompiler. frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); // No-op if the index is trivally convertable to an id. Label done; masm.branchTestInt32(Assembler::Equal, R0, &done); masm.branchTestString(Assembler::Equal, R0, &done); masm.branchTestSymbol(Assembler::Equal, R0, &done); prepareVMCall(); pushArg(R0); if (!callVM(ToIdInfo)) { return false; } masm.bind(&done); frame.pop(); // Pop index. frame.push(R0); return true; } typedef JSObject* (*ToAsyncFn)(JSContext*, HandleFunction); static const VMFunction ToAsyncInfo = FunctionInfo(js::WrapAsyncFunction, "ToAsync"); bool BaselineCompiler::emit_JSOP_TOASYNC() { frame.syncStack(0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); if (!callVM(ToAsyncInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.pop(); frame.push(R0); return true; } typedef JSObject* (*ToAsyncGenFn)(JSContext*, HandleFunction); static const VMFunction ToAsyncGenInfo = FunctionInfo(js::WrapAsyncGenerator, "ToAsyncGen"); bool BaselineCompiler::emit_JSOP_TOASYNCGEN() { frame.syncStack(0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); if (!callVM(ToAsyncGenInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.pop(); frame.push(R0); return true; } typedef JSObject* (*ToAsyncIterFn)(JSContext*, HandleObject, HandleValue); static const VMFunction ToAsyncIterInfo = FunctionInfo(js::CreateAsyncFromSyncIterator, "ToAsyncIter"); bool BaselineCompiler::emit_JSOP_TOASYNCITER() { frame.syncStack(0); masm.unboxObject(frame.addressOfStackValue(frame.peek(-2)), R0.scratchReg()); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); prepareVMCall(); pushArg(R1); pushArg(R0.scratchReg()); if (!callVM(ToAsyncIterInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.popn(2); frame.push(R0); return true; } typedef bool (*TrySkipAwaitFn)(JSContext*, HandleValue, MutableHandleValue); static const VMFunction TrySkipAwaitInfo = FunctionInfo(jit::TrySkipAwait, "TrySkipAwait"); bool BaselineCompiler::emit_JSOP_TRYSKIPAWAIT() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(R0); if (!callVM(TrySkipAwaitInfo)) { return false; } Label cannotSkip, done; masm.branchTestMagicValue(Assembler::Equal, R0, JS_CANNOT_SKIP_AWAIT, &cannotSkip); masm.moveValue(BooleanValue(true), R1); masm.jump(&done); masm.bind(&cannotSkip); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); masm.moveValue(BooleanValue(false), R1); masm.bind(&done); frame.pop(); frame.push(R0); frame.push(R1); return true; } typedef bool (*ThrowObjectCoercibleFn)(JSContext*, HandleValue); static const VMFunction ThrowObjectCoercibleInfo = FunctionInfo(ThrowObjectCoercible, "ThrowObjectCoercible"); bool BaselineCompiler::emit_JSOP_CHECKOBJCOERCIBLE() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); Label fail, done; masm.branchTestUndefined(Assembler::Equal, R0, &fail); masm.branchTestNull(Assembler::NotEqual, R0, &done); masm.bind(&fail); prepareVMCall(); pushArg(R0); if (!callVM(ThrowObjectCoercibleInfo)) { return false; } masm.bind(&done); return true; } typedef JSString* (*ToStringFn)(JSContext*, HandleValue); static const VMFunction ToStringInfo = FunctionInfo(ToStringSlow, "ToStringSlow"); bool BaselineCompiler::emit_JSOP_TOSTRING() { // Keep top stack value in R0. frame.popRegsAndSync(1); // Inline path for string. Label done; masm.branchTestString(Assembler::Equal, R0, &done); prepareVMCall(); pushArg(R0); // Call ToStringSlow which doesn't handle string inputs. if (!callVM(ToStringInfo)) { return false; } masm.tagValue(JSVAL_TYPE_STRING, ReturnReg, R0); masm.bind(&done); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_TABLESWITCH() { frame.popRegsAndSync(1); // Call IC. ICTableSwitch::Compiler compiler(cx, pc); return emitOpIC(compiler.getStub(&stubSpace_)); } bool BaselineCompiler::emit_JSOP_ITER() { frame.popRegsAndSync(1); ICGetIterator_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_MOREITER() { frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); ICIteratorMore_Fallback::Compiler compiler(cx); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } frame.push(R0); return true; } bool BaselineCompiler::emitIsMagicValue() { frame.syncStack(0); Label isMagic, done; masm.branchTestMagic(Assembler::Equal, frame.addressOfStackValue(frame.peek(-1)), &isMagic); masm.moveValue(BooleanValue(false), R0); masm.jump(&done); masm.bind(&isMagic); masm.moveValue(BooleanValue(true), R0); masm.bind(&done); frame.push(R0, JSVAL_TYPE_BOOLEAN); return true; } bool BaselineCompiler::emit_JSOP_ISNOITER() { return emitIsMagicValue(); } bool BaselineCompiler::emit_JSOP_ENDITER() { if (!emit_JSOP_JUMPTARGET()) { return false; } frame.popRegsAndSync(1); ICIteratorClose_Fallback::Compiler compiler(cx); return emitOpIC(compiler.getStub(&stubSpace_)); } bool BaselineCompiler::emit_JSOP_ISGENCLOSING() { return emitIsMagicValue(); } bool BaselineCompiler::emit_JSOP_GETRVAL() { frame.syncStack(0); emitLoadReturnValue(R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_SETRVAL() { // Store to the frame's return value slot. storeValue(frame.peek(-1), frame.addressOfReturnValue(), R2); masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); frame.pop(); return true; } bool BaselineCompiler::emit_JSOP_CALLEE() { MOZ_ASSERT(function()); frame.syncStack(0); masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), R0.scratchReg()); masm.tagValue(JSVAL_TYPE_OBJECT, R0.scratchReg(), R0); frame.push(R0); return true; } void BaselineCompiler::getThisEnvironmentCallee(Register reg) { // Directly load callee from frame if we have a HomeObject if (function() && function()->allowSuperProperty()) { masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), reg); return; } // Locate environment chain masm.loadPtr(frame.addressOfEnvironmentChain(), reg); // Walk environment chain until first non-arrow CallObject for (ScopeIter si(script->innermostScope(pc)); si; si++) { // Find first non-arrow FunctionScope if (si.hasSyntacticEnvironment() && si.scope()->is()) { JSFunction* fn = si.scope()->as().canonicalFunction(); if (!fn->isArrow()) { break; } } // Traverse environment chain if (si.scope()->hasEnvironment()) { Address nextAddr(reg, EnvironmentObject::offsetOfEnclosingEnvironment()); masm.unboxObject(nextAddr, reg); } } // Load callee masm.unboxObject(Address(reg, CallObject::offsetOfCallee()), reg); } typedef JSObject* (*HomeObjectSuperBaseFn)(JSContext*, HandleObject); static const VMFunction HomeObjectSuperBaseInfo = FunctionInfo(HomeObjectSuperBase, "HomeObjectSuperBase"); bool BaselineCompiler::emit_JSOP_SUPERBASE() { frame.syncStack(0); Register scratch = R0.scratchReg(); Register proto = R1.scratchReg(); // Lookup callee object of environment containing [[ThisValue]] getThisEnvironmentCallee(scratch); // Load [[HomeObject]] Address homeObjAddr(scratch, FunctionExtended::offsetOfMethodHomeObjectSlot()); #ifdef DEBUG Label isObject; masm.branchTestObject(Assembler::Equal, homeObjAddr, &isObject); masm.assumeUnreachable("[[HomeObject]] must be Object"); masm.bind(&isObject); #endif masm.unboxObject(homeObjAddr, scratch); // Load prototype from [[HomeObject]] masm.loadObjProto(scratch, proto); Label hasProto; MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1); masm.branchPtr(Assembler::Above, proto, ImmWord(1), &hasProto); // Use VMCall for missing or lazy proto prepareVMCall(); pushArg(scratch); // [[HomeObject]] if (!callVM(HomeObjectSuperBaseInfo)) { return false; } masm.movePtr(ReturnReg, proto); // Box prototype and return masm.bind(&hasProto); masm.tagValue(JSVAL_TYPE_OBJECT, proto, R1); frame.push(R1); return true; } typedef JSObject* (*SuperFunOperationFn)(JSContext*, HandleObject); static const VMFunction SuperFunOperationInfo = FunctionInfo(SuperFunOperation, "SuperFunOperation"); bool BaselineCompiler::emit_JSOP_SUPERFUN() { frame.syncStack(0); Register callee = R0.scratchReg(); Register proto = R1.scratchReg(); Register scratch = R2.scratchReg(); // Lookup callee object of environment containing [[ThisValue]] getThisEnvironmentCallee(callee); // Load prototype of callee masm.loadObjProto(callee, proto); // Use VMCall for missing or lazy proto Label needVMCall; MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1); masm.branchPtr(Assembler::BelowOrEqual, proto, ImmWord(1), &needVMCall); // Use VMCall for non-JSFunction objects (eg. Proxy) masm.branchTestObjClass(Assembler::NotEqual, proto, &JSFunction::class_, scratch, proto, &needVMCall); // Use VMCall if not constructor masm.load16ZeroExtend(Address(proto, JSFunction::offsetOfFlags()), scratch); masm.branchTest32(Assembler::Zero, scratch, Imm32(JSFunction::CONSTRUCTOR), &needVMCall); // Valid constructor Label hasSuperFun; masm.jump(&hasSuperFun); // Slow path VM Call masm.bind(&needVMCall); prepareVMCall(); pushArg(callee); if (!callVM(SuperFunOperationInfo)) { return false; } masm.movePtr(ReturnReg, proto); // Box prototype and return masm.bind(&hasSuperFun); masm.tagValue(JSVAL_TYPE_OBJECT, proto, R1); frame.push(R1); return true; } typedef bool (*NewArgumentsObjectFn)(JSContext*, BaselineFrame*, MutableHandleValue); static const VMFunction NewArgumentsObjectInfo = FunctionInfo(jit::NewArgumentsObject, "NewArgumentsObject"); bool BaselineCompiler::emit_JSOP_ARGUMENTS() { frame.syncStack(0); Label done; if (!script->argumentsHasVarBinding() || !script->needsArgsObj()) { // We assume the script does not need an arguments object. However, this // assumption can be invalidated later, see argumentsOptimizationFailed // in JSScript. Because we can't invalidate baseline JIT code, we set a // flag on BaselineScript when that happens and guard on it here. masm.moveValue(MagicValue(JS_OPTIMIZED_ARGUMENTS), R0); // Load script->baseline. Register scratch = R1.scratchReg(); masm.movePtr(ImmGCPtr(script), scratch); masm.loadPtr(Address(scratch, JSScript::offsetOfBaselineScript()), scratch); // If we don't need an arguments object, skip the VM call. masm.branchTest32(Assembler::Zero, Address(scratch, BaselineScript::offsetOfFlags()), Imm32(BaselineScript::NEEDS_ARGS_OBJ), &done); } prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVM(NewArgumentsObjectInfo)) { return false; } masm.bind(&done); frame.push(R0); return true; } typedef bool (*RunOnceScriptPrologueFn)(JSContext*, HandleScript); static const VMFunction RunOnceScriptPrologueInfo = FunctionInfo(js::RunOnceScriptPrologue, "RunOnceScriptPrologue"); bool BaselineCompiler::emit_JSOP_RUNONCE() { frame.syncStack(0); prepareVMCall(); masm.movePtr(ImmGCPtr(script), R0.scratchReg()); pushArg(R0.scratchReg()); return callVM(RunOnceScriptPrologueInfo); } bool BaselineCompiler::emit_JSOP_REST() { frame.syncStack(0); ArrayObject* templateObject = ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject, ObjectGroup::NewArrayKind::UnknownIndex); if (!templateObject) { return false; } // Call IC. ICRest_Fallback::Compiler compiler(cx, templateObject); if (!emitOpIC(compiler.getStub(&stubSpace_))) { return false; } // Mark R0 as pushed stack value. frame.push(R0); return true; } typedef JSObject* (*CreateGeneratorFn)(JSContext*, BaselineFrame*); static const VMFunction CreateGeneratorInfo = FunctionInfo(jit::CreateGenerator, "CreateGenerator"); bool BaselineCompiler::emit_JSOP_GENERATOR() { MOZ_ASSERT(frame.stackDepth() == 0); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); if (!callVM(CreateGeneratorInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_INITIALYIELD() { frame.syncStack(0); MOZ_ASSERT(frame.stackDepth() == 1); Register genObj = R2.scratchReg(); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), genObj); MOZ_ASSERT(GET_UINT24(pc) == 0); masm.storeValue(Int32Value(0), Address(genObj, GeneratorObject::offsetOfResumeIndexSlot())); Register envObj = R0.scratchReg(); Address envChainSlot(genObj, GeneratorObject::offsetOfEnvironmentChainSlot()); masm.loadPtr(frame.addressOfEnvironmentChain(), envObj); masm.guardedCallPreBarrier(envChainSlot, MIRType::Value); masm.storeValue(JSVAL_TYPE_OBJECT, envObj, envChainSlot); Register temp = R1.scratchReg(); Label skipBarrier; masm.branchPtrInNurseryChunk(Assembler::Equal, genObj, temp, &skipBarrier); masm.branchPtrInNurseryChunk(Assembler::NotEqual, envObj, temp, &skipBarrier); masm.push(genObj); MOZ_ASSERT(genObj == R2.scratchReg()); masm.call(&postBarrierSlot_); masm.pop(genObj); masm.bind(&skipBarrier); masm.tagValue(JSVAL_TYPE_OBJECT, genObj, JSReturnOperand); return emitReturn(); } typedef bool (*NormalSuspendFn)(JSContext*, HandleObject, BaselineFrame*, jsbytecode*, uint32_t); static const VMFunction NormalSuspendInfo = FunctionInfo(jit::NormalSuspend, "NormalSuspend"); bool BaselineCompiler::emit_JSOP_YIELD() { // Store generator in R0. frame.popRegsAndSync(1); Register genObj = R2.scratchReg(); masm.unboxObject(R0, genObj); MOZ_ASSERT(frame.stackDepth() >= 1); if (frame.stackDepth() == 1) { // If the expression stack is empty, we can inline the YIELD. masm.storeValue(Int32Value(GET_UINT24(pc)), Address(genObj, GeneratorObject::offsetOfResumeIndexSlot())); Register envObj = R0.scratchReg(); Address envChainSlot(genObj, GeneratorObject::offsetOfEnvironmentChainSlot()); masm.loadPtr(frame.addressOfEnvironmentChain(), envObj); masm.guardedCallPreBarrier(envChainSlot, MIRType::Value); masm.storeValue(JSVAL_TYPE_OBJECT, envObj, envChainSlot); Register temp = R1.scratchReg(); Label skipBarrier; masm.branchPtrInNurseryChunk(Assembler::Equal, genObj, temp, &skipBarrier); masm.branchPtrInNurseryChunk(Assembler::NotEqual, envObj, temp, &skipBarrier); MOZ_ASSERT(genObj == R2.scratchReg()); masm.call(&postBarrierSlot_); masm.bind(&skipBarrier); } else { masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); prepareVMCall(); pushArg(Imm32(frame.stackDepth())); pushArg(ImmPtr(pc)); pushArg(R1.scratchReg()); pushArg(genObj); if (!callVM(NormalSuspendInfo)) { return false; } } masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), JSReturnOperand); return emitReturn(); } bool BaselineCompiler::emit_JSOP_AWAIT() { return emit_JSOP_YIELD(); } typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*); static const VMFunction DebugAfterYieldInfo = FunctionInfo(jit::DebugAfterYield, "DebugAfterYield"); bool BaselineCompiler::emit_JSOP_DEBUGAFTERYIELD() { if (!compileDebugInstrumentation_) { return true; } frame.assertSyncedStack(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); prepareVMCall(); pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); if (!callVM(DebugAfterYieldInfo)) { return false; } retAddrEntries_.back().setKind(RetAddrEntry::Kind::DebugAfterYield); Label done; masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); { masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); masm.jump(&return_); } masm.bind(&done); return true; } typedef bool (*FinalSuspendFn)(JSContext*, HandleObject, jsbytecode*); static const VMFunction FinalSuspendInfo = FunctionInfo(jit::FinalSuspend, "FinalSuspend"); bool BaselineCompiler::emit_JSOP_FINALYIELDRVAL() { // Store generator in R0. frame.popRegsAndSync(1); masm.unboxObject(R0, R0.scratchReg()); prepareVMCall(); pushArg(ImmPtr(pc)); pushArg(R0.scratchReg()); if (!callVM(FinalSuspendInfo)) { return false; } masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); return emitReturn(); } typedef bool (*InterpretResumeFn)(JSContext*, HandleObject, HandleValue, HandlePropertyName, MutableHandleValue); static const VMFunction InterpretResumeInfo = FunctionInfo(jit::InterpretResume, "InterpretResume"); typedef bool (*GeneratorThrowFn)(JSContext*, BaselineFrame*, Handle, HandleValue, uint32_t); static const VMFunction GeneratorThrowOrReturnInfo = FunctionInfo(jit::GeneratorThrowOrReturn, "GeneratorThrowOrReturn", TailCall); bool BaselineCompiler::emit_JSOP_RESUME() { GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc); frame.syncStack(0); masm.assertStackAlignment(sizeof(Value), 0); AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(BaselineFrameReg); // Load generator object. Register genObj = regs.takeAny(); masm.unboxObject(frame.addressOfStackValue(frame.peek(-2)), genObj); // Load callee. Register callee = regs.takeAny(); masm.unboxObject(Address(genObj, GeneratorObject::offsetOfCalleeSlot()), callee); // Load the script. Note that we don't relazify generator scripts, so it's // guaranteed to be non-lazy. Register scratch1 = regs.takeAny(); masm.loadPtr(Address(callee, JSFunction::offsetOfScript()), scratch1); // Load the BaselineScript or call a stub if we don't have one. Label interpret; masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1); masm.branchPtr(Assembler::BelowOrEqual, scratch1, ImmPtr(BASELINE_DISABLED_SCRIPT), &interpret); #ifdef JS_TRACE_LOGGING if (!emitTraceLoggerResume(scratch1, regs)) { return false; } #endif // Push |undefined| for all formals. Register scratch2 = regs.takeAny(); Label loop, loopDone; masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch2); masm.bind(&loop); masm.branchTest32(Assembler::Zero, scratch2, scratch2, &loopDone); { masm.pushValue(UndefinedValue()); masm.sub32(Imm32(1), scratch2); masm.jump(&loop); } masm.bind(&loopDone); // Push |undefined| for |this|. masm.pushValue(UndefinedValue()); // Update BaselineFrame frameSize field and create the frame descriptor. masm.computeEffectiveAddress(Address(BaselineFrameReg, BaselineFrame::FramePointerOffset), scratch2); masm.subStackPtrFrom(scratch2); masm.store32(scratch2, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); masm.makeFrameDescriptor(scratch2, FrameType::BaselineJS, JitFrameLayout::Size()); masm.Push(Imm32(0)); // actual argc masm.PushCalleeToken(callee, /* constructing = */ false); masm.Push(scratch2); // frame descriptor regs.add(callee); // Load the return value. ValueOperand retVal = regs.takeAnyValue(); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), retVal); // Push a fake return address on the stack. We will resume here when the // generator returns. Label genStart, returnTarget; #ifdef JS_USE_LINK_REGISTER masm.call(&genStart); #else masm.callAndPushReturnAddress(&genStart); #endif // Add a RetAddrEntry so the return offset -> pc mapping works. if (!appendRetAddrEntry(RetAddrEntry::Kind::IC, masm.currentOffset())) { return false; } masm.jump(&returnTarget); masm.bind(&genStart); #ifdef JS_USE_LINK_REGISTER masm.pushReturnAddress(); #endif // If profiler instrumentation is on, update lastProfilingFrame on // current JitActivation { Register scratchReg = scratch2; Label skip; AbsoluteAddress addressOfEnabled(cx->runtime()->geckoProfiler().addressOfEnabled()); masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skip); masm.loadJSContext(scratchReg); masm.loadPtr(Address(scratchReg, JSContext::offsetOfProfilingActivation()), scratchReg); masm.storeStackPtr(Address(scratchReg, JitActivation::offsetOfLastProfilingFrame())); masm.bind(&skip); } // Construct BaselineFrame. masm.push(BaselineFrameReg); masm.moveStackPtrTo(BaselineFrameReg); masm.subFromStackPtr(Imm32(BaselineFrame::Size())); masm.assertStackAlignment(sizeof(Value), 0); // Store flags and env chain. masm.store32(Imm32(BaselineFrame::HAS_INITIAL_ENV), frame.addressOfFlags()); masm.unboxObject(Address(genObj, GeneratorObject::offsetOfEnvironmentChainSlot()), scratch2); masm.storePtr(scratch2, frame.addressOfEnvironmentChain()); // Store the arguments object if there is one. Label noArgsObj; Address argsObjSlot(genObj, GeneratorObject::offsetOfArgsObjSlot()); masm.branchTestUndefined(Assembler::Equal, argsObjSlot, &noArgsObj); masm.unboxObject(argsObjSlot, scratch2); { masm.storePtr(scratch2, frame.addressOfArgsObj()); masm.or32(Imm32(BaselineFrame::HAS_ARGS_OBJ), frame.addressOfFlags()); } masm.bind(&noArgsObj); // Push expression slots if needed. Label noExprStack; Address exprStackSlot(genObj, GeneratorObject::offsetOfExpressionStackSlot()); masm.branchTestNull(Assembler::Equal, exprStackSlot, &noExprStack); { masm.unboxObject(exprStackSlot, scratch2); Register initLength = regs.takeAny(); masm.loadPtr(Address(scratch2, NativeObject::offsetOfElements()), scratch2); masm.load32(Address(scratch2, ObjectElements::offsetOfInitializedLength()), initLength); masm.store32(Imm32(0), Address(scratch2, ObjectElements::offsetOfInitializedLength())); Label loop, loopDone; masm.bind(&loop); masm.branchTest32(Assembler::Zero, initLength, initLength, &loopDone); { masm.pushValue(Address(scratch2, 0)); masm.guardedCallPreBarrier(Address(scratch2, 0), MIRType::Value); masm.addPtr(Imm32(sizeof(Value)), scratch2); masm.sub32(Imm32(1), initLength); masm.jump(&loop); } masm.bind(&loopDone); regs.add(initLength); } masm.bind(&noExprStack); masm.pushValue(retVal); masm.switchToObjectRealm(genObj, scratch2); if (resumeKind == GeneratorObject::NEXT) { // Determine the resume address based on the resumeIndex and the // resumeIndex -> native table in the BaselineScript. masm.load32(Address(scratch1, BaselineScript::offsetOfResumeEntriesOffset()), scratch2); masm.addPtr(scratch2, scratch1); masm.unboxInt32(Address(genObj, GeneratorObject::offsetOfResumeIndexSlot()), scratch2); masm.loadPtr(BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))), scratch1); // Mark as running and jump to the generator's JIT code. masm.storeValue(Int32Value(GeneratorObject::RESUME_INDEX_RUNNING), Address(genObj, GeneratorObject::offsetOfResumeIndexSlot())); masm.jump(scratch1); } else { MOZ_ASSERT(resumeKind == GeneratorObject::THROW || resumeKind == GeneratorObject::RETURN); // Update the frame's frameSize field. masm.computeEffectiveAddress(Address(BaselineFrameReg, BaselineFrame::FramePointerOffset), scratch2); masm.movePtr(scratch2, scratch1); masm.subStackPtrFrom(scratch2); masm.store32(scratch2, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); masm.loadBaselineFramePtr(BaselineFrameReg, scratch2); prepareVMCall(); pushArg(Imm32(resumeKind)); pushArg(retVal); pushArg(genObj); pushArg(scratch2); TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(GeneratorThrowOrReturnInfo); // Create and push the frame descriptor. masm.subStackPtrFrom(scratch1); masm.makeFrameDescriptor(scratch1, FrameType::BaselineJS, ExitFrameLayout::Size()); masm.push(scratch1); // We have created a baseline frame as if we were the // callee. However, if we just did a regular call at this // point, our return address would be bogus: it would point at // self-hosted code, instead of the generator code that we are // pretending we are already executing. Instead, we push a // dummy return address. In jit::GeneratorThrowOrReturn, // we will set the baseline frame's overridePc. Frame iterators // will use the override pc instead of relying on the return // address. // On ARM64, the callee will push a bogus return address. On // other architectures, we push a null return address. #ifndef JS_CODEGEN_ARM64 masm.push(ImmWord(0)); #endif masm.jump(code); } // If the generator script has no JIT code, call into the VM. masm.bind(&interpret); prepareVMCall(); if (resumeKind == GeneratorObject::NEXT) { pushArg(ImmGCPtr(cx->names().next)); } else if (resumeKind == GeneratorObject::THROW) { pushArg(ImmGCPtr(cx->names().throw_)); } else { MOZ_ASSERT(resumeKind == GeneratorObject::RETURN); pushArg(ImmGCPtr(cx->names().return_)); } masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), retVal); pushArg(retVal); pushArg(genObj); if (!callVM(InterpretResumeInfo)) { return false; } // After the generator returns, we restore the stack pointer, switch back to // the current realm, push the return value, and we're done. masm.bind(&returnTarget); masm.computeEffectiveAddress(frame.addressOfStackValue(frame.peek(-1)), masm.getStackPointer()); masm.switchToRealm(script->realm(), R2.scratchReg()); frame.popn(2); frame.push(R0); return true; } typedef bool (*CheckSelfHostedFn)(JSContext*, HandleValue); static const VMFunction CheckSelfHostedInfo = FunctionInfo(js::Debug_CheckSelfHosted, "Debug_CheckSelfHosted"); bool BaselineCompiler::emit_JSOP_DEBUGCHECKSELFHOSTED() { #ifdef DEBUG frame.syncStack(0); masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(R0); if (!callVM(CheckSelfHostedInfo)) { return false; } #endif return true; } bool BaselineCompiler::emit_JSOP_IS_CONSTRUCTING() { frame.push(MagicValue(JS_IS_CONSTRUCTING)); return true; } bool BaselineCompiler::emit_JSOP_JUMPTARGET() { if (!script->hasScriptCounts()) { return true; } PCCounts* counts = script->maybeGetPCCounts(pc); uint64_t* counterAddr = &counts->numExec(); masm.inc64(AbsoluteAddress(counterAddr)); return true; } typedef bool (*CheckClassHeritageOperationFn)(JSContext*, HandleValue); static const VMFunction CheckClassHeritageOperationInfo = FunctionInfo(js::CheckClassHeritageOperation, "CheckClassHeritageOperation"); bool BaselineCompiler::emit_JSOP_CHECKCLASSHERITAGE() { frame.syncStack(0); // Leave the heritage value on the stack. masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(R0); return callVM(CheckClassHeritageOperationInfo); } bool BaselineCompiler::emit_JSOP_INITHOMEOBJECT() { frame.syncStack(0); // Load HomeObject off stack unsigned skipOver = GET_UINT8(pc); MOZ_ASSERT(frame.stackDepth() >= skipOver + 2); masm.loadValue(frame.addressOfStackValue(frame.peek(-2 - skipOver)), R0); // Load function off stack Register func = R2.scratchReg(); masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), func); // Set HOMEOBJECT_SLOT Address addr(func, FunctionExtended::offsetOfMethodHomeObjectSlot()); masm.guardedCallPreBarrier(addr, MIRType::Value); masm.storeValue(R0, addr); Register temp = R1.scratchReg(); Label skipBarrier; masm.branchPtrInNurseryChunk(Assembler::Equal, func, temp, &skipBarrier); masm.branchValueIsNurseryObject(Assembler::NotEqual, R0, temp, &skipBarrier); masm.call(&postBarrierSlot_); masm.bind(&skipBarrier); return true; } bool BaselineCompiler::emit_JSOP_BUILTINPROTO() { // The builtin prototype is a constant for a given global. JSProtoKey key = static_cast(GET_UINT8(pc)); MOZ_ASSERT(key < JSProto_LIMIT); JSObject* builtin = GlobalObject::getOrCreatePrototype(cx, key); if (!builtin) { return false; } frame.push(ObjectValue(*builtin)); return true; } typedef JSObject* (*ObjectWithProtoOperationFn)(JSContext*, HandleValue); static const VMFunction ObjectWithProtoOperationInfo = FunctionInfo(js::ObjectWithProtoOperation, "ObjectWithProtoOperationInfo"); bool BaselineCompiler::emit_JSOP_OBJWITHPROTO() { frame.syncStack(0); // Leave the proto value on the stack for the decompiler masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); prepareVMCall(); pushArg(R0); if (!callVM(ObjectWithProtoOperationInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.pop(); frame.push(R0); return true; } typedef JSObject* (*FunWithProtoFn)(JSContext*, HandleFunction, HandleObject, HandleObject); static const VMFunction FunWithProtoInfo = FunctionInfo(js::FunWithProtoOperation, "FunWithProtoOperation"); bool BaselineCompiler::emit_JSOP_FUNWITHPROTO() { frame.popRegsAndSync(1); masm.unboxObject(R0, R0.scratchReg()); masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(R1.scratchReg()); pushArg(ImmGCPtr(script->getFunction(GET_UINT32_INDEX(pc)))); if (!callVM(FunWithProtoInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef JSFunction* (*MakeDefaultConstructorFn)(JSContext*, HandleScript, jsbytecode*, HandleObject); static const VMFunction MakeDefaultConstructorInfo = FunctionInfo(js::MakeDefaultConstructor, "MakeDefaultConstructor"); bool BaselineCompiler::emit_JSOP_CLASSCONSTRUCTOR() { frame.syncStack(0); // Pass nullptr as prototype to MakeDefaultConstructor prepareVMCall(); pushArg(ImmPtr(nullptr)); pushArg(ImmPtr(pc)); pushArg(ImmGCPtr(script)); if (!callVM(MakeDefaultConstructorInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_DERIVEDCONSTRUCTOR() { frame.popRegsAndSync(1); masm.unboxObject(R0, R0.scratchReg()); prepareVMCall(); pushArg(R0.scratchReg()); pushArg(ImmPtr(pc)); pushArg(ImmGCPtr(script)); if (!callVM(MakeDefaultConstructorInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef JSObject* (*GetOrCreateModuleMetaObjectFn)(JSContext*, HandleObject); static const VMFunction GetOrCreateModuleMetaObjectInfo = FunctionInfo(js::GetOrCreateModuleMetaObject, "GetOrCreateModuleMetaObject"); bool BaselineCompiler::emit_JSOP_IMPORTMETA() { RootedModuleObject module(cx, GetModuleObjectForScript(script)); MOZ_ASSERT(module); frame.syncStack(0); prepareVMCall(); pushArg(ImmGCPtr(module)); if (!callVM(GetOrCreateModuleMetaObjectInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } typedef JSObject* (*StartDynamicModuleImportFn)(JSContext*, HandleValue, HandleValue); static const VMFunction StartDynamicModuleImportInfo = FunctionInfo(js::StartDynamicModuleImport, "StartDynamicModuleImport"); bool BaselineCompiler::emit_JSOP_DYNAMIC_IMPORT() { RootedValue referencingPrivate(cx, FindScriptOrModulePrivateForScript(script)); // Put specifier value in R0. frame.popRegsAndSync(1); prepareVMCall(); pushArg(R0); pushArg(referencingPrivate); if (!callVM(StartDynamicModuleImportInfo)) { return false; } masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); frame.push(R0); return true; } diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -1,495 +1,496 @@ /* -*- 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/. */ #ifndef jit_BaselineCompiler_h #define jit_BaselineCompiler_h #include "jit/BaselineFrameInfo.h" #include "jit/BaselineIC.h" #include "jit/BytecodeAnalysis.h" #include "jit/FixedList.h" #include "jit/MacroAssembler.h" namespace js { namespace jit { #define OPCODE_LIST(_) \ _(JSOP_NOP) \ _(JSOP_NOP_DESTRUCTURING) \ _(JSOP_LABEL) \ _(JSOP_ITERNEXT) \ _(JSOP_POP) \ _(JSOP_POPN) \ _(JSOP_DUPAT) \ _(JSOP_ENTERWITH) \ _(JSOP_LEAVEWITH) \ _(JSOP_DUP) \ _(JSOP_DUP2) \ _(JSOP_SWAP) \ _(JSOP_PICK) \ _(JSOP_UNPICK) \ _(JSOP_GOTO) \ _(JSOP_IFEQ) \ _(JSOP_IFNE) \ _(JSOP_AND) \ _(JSOP_OR) \ _(JSOP_NOT) \ _(JSOP_POS) \ _(JSOP_LOOPHEAD) \ _(JSOP_LOOPENTRY) \ _(JSOP_VOID) \ _(JSOP_UNDEFINED) \ _(JSOP_HOLE) \ _(JSOP_NULL) \ _(JSOP_TRUE) \ _(JSOP_FALSE) \ _(JSOP_ZERO) \ _(JSOP_ONE) \ _(JSOP_INT8) \ _(JSOP_INT32) \ _(JSOP_UINT16) \ _(JSOP_UINT24) \ + _(JSOP_RESUMEINDEX) \ _(JSOP_DOUBLE) \ _(JSOP_STRING) \ _(JSOP_SYMBOL) \ _(JSOP_OBJECT) \ _(JSOP_CALLSITEOBJ) \ _(JSOP_REGEXP) \ _(JSOP_LAMBDA) \ _(JSOP_LAMBDA_ARROW) \ _(JSOP_SETFUNNAME) \ _(JSOP_BITOR) \ _(JSOP_BITXOR) \ _(JSOP_BITAND) \ _(JSOP_LSH) \ _(JSOP_RSH) \ _(JSOP_URSH) \ _(JSOP_ADD) \ _(JSOP_SUB) \ _(JSOP_MUL) \ _(JSOP_DIV) \ _(JSOP_MOD) \ _(JSOP_POW) \ _(JSOP_LT) \ _(JSOP_LE) \ _(JSOP_GT) \ _(JSOP_GE) \ _(JSOP_EQ) \ _(JSOP_NE) \ _(JSOP_STRICTEQ) \ _(JSOP_STRICTNE) \ _(JSOP_CONDSWITCH) \ _(JSOP_CASE) \ _(JSOP_DEFAULT) \ _(JSOP_LINENO) \ _(JSOP_BITNOT) \ _(JSOP_NEG) \ _(JSOP_NEWARRAY) \ _(JSOP_NEWARRAY_COPYONWRITE) \ _(JSOP_INITELEM_ARRAY) \ _(JSOP_NEWOBJECT) \ _(JSOP_NEWINIT) \ _(JSOP_INITELEM) \ _(JSOP_INITELEM_GETTER) \ _(JSOP_INITELEM_SETTER) \ _(JSOP_INITELEM_INC) \ _(JSOP_MUTATEPROTO) \ _(JSOP_INITPROP) \ _(JSOP_INITLOCKEDPROP) \ _(JSOP_INITHIDDENPROP) \ _(JSOP_INITPROP_GETTER) \ _(JSOP_INITPROP_SETTER) \ _(JSOP_GETELEM) \ _(JSOP_SETELEM) \ _(JSOP_STRICTSETELEM) \ _(JSOP_CALLELEM) \ _(JSOP_DELELEM) \ _(JSOP_STRICTDELELEM) \ _(JSOP_GETELEM_SUPER) \ _(JSOP_SETELEM_SUPER) \ _(JSOP_STRICTSETELEM_SUPER) \ _(JSOP_IN) \ _(JSOP_HASOWN) \ _(JSOP_GETGNAME) \ _(JSOP_BINDGNAME) \ _(JSOP_SETGNAME) \ _(JSOP_STRICTSETGNAME) \ _(JSOP_SETNAME) \ _(JSOP_STRICTSETNAME) \ _(JSOP_GETPROP) \ _(JSOP_SETPROP) \ _(JSOP_STRICTSETPROP) \ _(JSOP_CALLPROP) \ _(JSOP_DELPROP) \ _(JSOP_STRICTDELPROP) \ _(JSOP_GETPROP_SUPER) \ _(JSOP_SETPROP_SUPER) \ _(JSOP_STRICTSETPROP_SUPER) \ _(JSOP_LENGTH) \ _(JSOP_GETBOUNDNAME) \ _(JSOP_GETALIASEDVAR) \ _(JSOP_SETALIASEDVAR) \ _(JSOP_GETNAME) \ _(JSOP_BINDNAME) \ _(JSOP_DELNAME) \ _(JSOP_GETIMPORT) \ _(JSOP_GETINTRINSIC) \ _(JSOP_BINDVAR) \ _(JSOP_DEFVAR) \ _(JSOP_DEFCONST) \ _(JSOP_DEFLET) \ _(JSOP_DEFFUN) \ _(JSOP_GETLOCAL) \ _(JSOP_SETLOCAL) \ _(JSOP_GETARG) \ _(JSOP_SETARG) \ _(JSOP_CHECKLEXICAL) \ _(JSOP_INITLEXICAL) \ _(JSOP_INITGLEXICAL) \ _(JSOP_CHECKALIASEDLEXICAL) \ _(JSOP_INITALIASEDLEXICAL) \ _(JSOP_UNINITIALIZED) \ _(JSOP_CALL) \ _(JSOP_CALL_IGNORES_RV) \ _(JSOP_CALLITER) \ _(JSOP_FUNCALL) \ _(JSOP_FUNAPPLY) \ _(JSOP_NEW) \ _(JSOP_EVAL) \ _(JSOP_STRICTEVAL) \ _(JSOP_SPREADCALL) \ _(JSOP_SPREADNEW) \ _(JSOP_SPREADEVAL) \ _(JSOP_STRICTSPREADEVAL) \ _(JSOP_OPTIMIZE_SPREADCALL)\ _(JSOP_IMPLICITTHIS) \ _(JSOP_GIMPLICITTHIS) \ _(JSOP_INSTANCEOF) \ _(JSOP_TYPEOF) \ _(JSOP_TYPEOFEXPR) \ _(JSOP_THROWMSG) \ _(JSOP_THROW) \ _(JSOP_THROWING) \ _(JSOP_TRY) \ _(JSOP_FINALLY) \ _(JSOP_GOSUB) \ _(JSOP_RETSUB) \ _(JSOP_PUSHLEXICALENV) \ _(JSOP_POPLEXICALENV) \ _(JSOP_FRESHENLEXICALENV) \ _(JSOP_RECREATELEXICALENV) \ _(JSOP_DEBUGLEAVELEXICALENV) \ _(JSOP_PUSHVARENV) \ _(JSOP_POPVARENV) \ _(JSOP_EXCEPTION) \ _(JSOP_DEBUGGER) \ _(JSOP_ARGUMENTS) \ _(JSOP_RUNONCE) \ _(JSOP_REST) \ _(JSOP_TOASYNC) \ _(JSOP_TOASYNCGEN) \ _(JSOP_TOASYNCITER) \ _(JSOP_TOID) \ _(JSOP_TOSTRING) \ _(JSOP_TABLESWITCH) \ _(JSOP_ITER) \ _(JSOP_MOREITER) \ _(JSOP_ISNOITER) \ _(JSOP_ENDITER) \ _(JSOP_ISGENCLOSING) \ _(JSOP_GENERATOR) \ _(JSOP_INITIALYIELD) \ _(JSOP_YIELD) \ _(JSOP_AWAIT) \ _(JSOP_TRYSKIPAWAIT) \ _(JSOP_DEBUGAFTERYIELD) \ _(JSOP_FINALYIELDRVAL) \ _(JSOP_RESUME) \ _(JSOP_CALLEE) \ _(JSOP_SUPERBASE) \ _(JSOP_SUPERFUN) \ _(JSOP_GETRVAL) \ _(JSOP_SETRVAL) \ _(JSOP_RETRVAL) \ _(JSOP_RETURN) \ _(JSOP_FUNCTIONTHIS) \ _(JSOP_GLOBALTHIS) \ _(JSOP_CHECKISOBJ) \ _(JSOP_CHECKISCALLABLE) \ _(JSOP_CHECKTHIS) \ _(JSOP_CHECKTHISREINIT) \ _(JSOP_CHECKRETURN) \ _(JSOP_NEWTARGET) \ _(JSOP_SUPERCALL) \ _(JSOP_SPREADSUPERCALL) \ _(JSOP_THROWSETCONST) \ _(JSOP_THROWSETALIASEDCONST) \ _(JSOP_THROWSETCALLEE) \ _(JSOP_INITHIDDENPROP_GETTER) \ _(JSOP_INITHIDDENPROP_SETTER) \ _(JSOP_INITHIDDENELEM) \ _(JSOP_INITHIDDENELEM_GETTER) \ _(JSOP_INITHIDDENELEM_SETTER) \ _(JSOP_CHECKOBJCOERCIBLE) \ _(JSOP_DEBUGCHECKSELFHOSTED) \ _(JSOP_JUMPTARGET) \ _(JSOP_IS_CONSTRUCTING) \ _(JSOP_TRY_DESTRUCTURING_ITERCLOSE) \ _(JSOP_CHECKCLASSHERITAGE) \ _(JSOP_INITHOMEOBJECT) \ _(JSOP_BUILTINPROTO) \ _(JSOP_OBJWITHPROTO) \ _(JSOP_FUNWITHPROTO) \ _(JSOP_CLASSCONSTRUCTOR) \ _(JSOP_DERIVEDCONSTRUCTOR) \ _(JSOP_IMPORTMETA) \ _(JSOP_DYNAMIC_IMPORT) class BaselineCompiler final { JSContext* cx; JSScript* script; jsbytecode* pc; StackMacroAssembler masm; bool ionCompileable_; bool compileDebugInstrumentation_; TempAllocator& alloc_; BytecodeAnalysis analysis_; FrameInfo frame; FallbackICStubSpace stubSpace_; js::Vector icEntries_; js::Vector retAddrEntries_; // Stores the native code offset for a bytecode pc. struct PCMappingEntry { uint32_t pcOffset; uint32_t nativeOffset; PCMappingSlotInfo slotInfo; // If set, insert a PCMappingIndexEntry before encoding the // current entry. bool addIndexEntry; }; js::Vector pcMappingEntries_; // Labels for the 'movWithPatch' for loading IC entry pointers in // the generated IC-calling code in the main jitcode. These need // to be patched with the actual icEntry offsets after the BaselineScript // has been allocated. struct ICLoadLabel { size_t icEntry; CodeOffset label; }; js::Vector icLoadLabels_; uint32_t pushedBeforeCall_; #ifdef DEBUG bool inCall_; #endif CodeOffset profilerPushToggleOffset_; CodeOffset profilerEnterFrameToggleOffset_; CodeOffset profilerExitFrameToggleOffset_; Vector traceLoggerToggleOffsets_; CodeOffset traceLoggerScriptTextIdOffset_; FixedList