From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/frontend/BytecodeEmitter.cpp | 12974 ++++++++++++++++++++++++++++++++++ 1 file changed, 12974 insertions(+) create mode 100644 js/src/frontend/BytecodeEmitter.cpp (limited to 'js/src/frontend/BytecodeEmitter.cpp') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp new file mode 100644 index 0000000000..df44743768 --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -0,0 +1,12974 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/Casting.h" // mozilla::AssertedCast +#include "mozilla/DebugOnly.h" // mozilla::DebugOnly +#include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32 +#include "mozilla/HashTable.h" // mozilla::HashSet +#include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some} +#include "mozilla/PodOperations.h" // mozilla::PodCopy +#include "mozilla/Saturate.h" +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include +#include +#include + +#include "jstypes.h" // JS_BIT + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl +#include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter +#include "frontend/CForEmitter.h" // CForEmitter +#include "frontend/DecoratorEmitter.h" // DecoratorEmitter +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DoWhileEmitter.h" // DoWhileEmitter +#include "frontend/ElemOpEmitter.h" // ElemOpEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter +#include "frontend/ForInEmitter.h" // ForInEmitter +#include "frontend/ForOfEmitter.h" // ForOfEmitter +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "frontend/LabelEmitter.h" // LabelEmitter +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter +#include "frontend/ModuleSharedContext.h" // ModuleSharedContext +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/NameFunctions.h" // NameFunctions +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter +#include "frontend/OptionalEmitter.h" // OptionalEmitter +#include "frontend/ParseContext.h" // ParseContext::Scope +#include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses +#include "frontend/Parser.h" // Parser +#include "frontend/ParserAtom.h" // ParserAtomsTable, ParserAtom +#include "frontend/PrivateOpEmitter.h" // PrivateOpEmitter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter +#include "frontend/SwitchEmitter.h" // SwitchEmitter +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "frontend/TryEmitter.h" // TryEmitter +#include "frontend/WhileEmitter.h" // WhileEmitter +#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/friend/StackLimits.h" // AutoCheckRecursionLimit +#include "util/StringBuffer.h" // StringBuffer +#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget +#include "vm/CompletionKind.h" // CompletionKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorObject.h" // AbstractGeneratorObject +#include "vm/Opcodes.h" // JSOp, JSOpLength_* +#include "vm/PropMap.h" // SharedPropMap::MaxPropsForNonDictionary +#include "vm/Scope.h" // GetScopeDataTrailingNames +#include "vm/SharedStencil.h" // ScopeNote +#include "vm/ThrowMsgKind.h" // ThrowMsgKind + +using namespace js; +using namespace js::frontend; + +using mozilla::AssertedCast; +using mozilla::AsVariant; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::NumberEqualsInt32; +using mozilla::NumberIsInt32; +using mozilla::PodCopy; +using mozilla::Some; + +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::WhileStmt || kind == ParseNodeKind::ForStmt || + kind == ParseNodeKind::Function; +} + +static bool NeedsFieldInitializer(ParseNode* member, bool inStaticContext) { + // For the purposes of bytecode emission, StaticClassBlocks are treated as if + // they were static initializers. + return (member->is() && inStaticContext) || + (member->is() && + member->as().isStatic() == inStaticContext); +} + +static bool NeedsAccessorInitializer(ParseNode* member, bool isStatic) { + if (isStatic) { + return false; + } + return member->is() && + member->as().name().isKind(ParseNodeKind::PrivateName) && + !member->as().isStatic() && + member->as().accessorType() != AccessorType::None; +} + +static bool ShouldSuppressBreakpointsAndSourceNotes( + SharedContext* sc, BytecodeEmitter::EmitterMode emitterMode) { + // Suppress for all self-hosting code. + if (emitterMode == BytecodeEmitter::EmitterMode::SelfHosting) { + return true; + } + + // Suppress for synthesized class constructors. + if (sc->isFunctionBox()) { + FunctionBox* funbox = sc->asFunctionBox(); + return funbox->isSyntheticFunction() && funbox->isClassConstructor(); + } + + return false; +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc, + SharedContext* sc, + const ErrorReporter& errorReporter, + CompilationState& compilationState, + EmitterMode emitterMode) + : sc(sc), + fc(fc), + parent(parent), + bytecodeSection_(fc, sc->extent().lineno, + JS::LimitedColumnNumberOneOrigin(sc->extent().column)), + perScriptData_(fc, compilationState), + errorReporter_(errorReporter), + compilationState(compilationState), + suppressBreakpointsAndSourceNotes( + ShouldSuppressBreakpointsAndSourceNotes(sc, emitterMode)), + emitterMode(emitterMode) { + MOZ_ASSERT_IF(parent, fc == parent->fc); +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc) + : BytecodeEmitter(parent, parent->fc, sc, parent->errorReporter_, + parent->compilationState, parent->emitterMode) {} + +BytecodeEmitter::BytecodeEmitter(FrontendContext* fc, + const EitherParser& parser, SharedContext* sc, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(nullptr, fc, sc, parser.errorReporter(), compilationState, + emitterMode) { + ep_.emplace(parser); +} + +void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); +} + +bool BytecodeEmitter::init() { + if (!parent) { + if (!compilationState.prepareSharedDataStorage(fc)) { + return false; + } + } + return perScriptData_.init(fc); +} + +bool BytecodeEmitter::init(TokenPos bodyPosition) { + initFromBodyPosition(bodyPosition); + return init(); +} + +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(TaggedParserAtomIndex name) { + return innermostEmitterScope()->lookup(this, name); +} + +void BytecodeEmitter::lookupPrivate(TaggedParserAtomIndex name, + NameLocation& loc, + Maybe& brandLoc) { + innermostEmitterScope()->lookupPrivate(this, name, loc, brandLoc); +} + +Maybe BytecodeEmitter::locationOfNameBoundInScope( + TaggedParserAtomIndex name, EmitterScope* target) { + return innermostEmitterScope()->locationBoundInScope(name, target); +} + +template +Maybe BytecodeEmitter::locationOfNameBoundInScopeType( + TaggedParserAtomIndex name, EmitterScope* source) { + EmitterScope* aScope = source; + while (!aScope->scope(this).is()) { + aScope = aScope->enclosingInFrame(); + } + return source->locationBoundInScope(name, aScope); +} + +bool BytecodeEmitter::markStepBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + if (!newSrcNote(SrcNoteType::BreakpointStepSep)) { + return false; + } + + // We track the location of the most recent separator for use in + // markSimpleBreakpoint. Note that this means that the position must already + // be set before markStepBreakpoint is called. + bytecodeSection().updateSeparatorPosition(); + + return true; +} + +bool BytecodeEmitter::markSimpleBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + // If a breakable call ends up being the same location as the most recent + // expression start, we need to skip marking it breakable in order to avoid + // having two breakpoints with the same line/column position. + // Note: This assumes that the position for the call has already been set. + if (!bytecodeSection().isDuplicateLocation()) { + if (!newSrcNote(SrcNoteType::Breakpoint)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta, + BytecodeOffset* offset) { + size_t oldLength = bytecodeSection().code().length(); + *offset = BytecodeOffset(oldLength); + + size_t newLength = oldLength + size_t(delta); + if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) { + ReportAllocationOverflow(fc); + return false; + } + + if (!bytecodeSection().code().growByUninitialized(delta)) { + return false; + } + + if (BytecodeOpHasIC(op)) { + // Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT + // arguments, numICEntries cannot overflow. + static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX, + "numICEntries must not overflow"); + bytecodeSection().incrementNumICEntries(); + } + + return true; +} + +#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)); + + BytecodeOffset offset; + if (!emitCheck(op, 1, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 2, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = jsbytecode(op1); + bytecodeSection().updateDepth(op, 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)); + + BytecodeOffset offset; + if (!emitCheck(op, 3, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = op1; + code[2] = op2; + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + ptrdiff_t length = 1 + ptrdiff_t(extra); + + BytecodeOffset off; + if (!emitCheck(op, length, &off)) { + return false; + } + + jsbytecode* code = bytecodeSection().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) { + bytecodeSection().updateDepth(op, off); + } + + if (offset) { + *offset = off; + } + return true; +} + +bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) { + MOZ_ASSERT(BytecodeIsJumpTarget(op)); + + // Record the current IC-entry index at start of this op. + uint32_t numEntries = bytecodeSection().numICEntries(); + + size_t n = GetOpLength(op) - 1; + MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN); + + if (!emitN(op, n, off)) { + return false; + } + + SET_ICINDEX(bytecodeSection().code(*off), numEntries); + return true; +} + +bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) { + BytecodeOffset off = bytecodeSection().offset(); + + // Alias consecutive jump targets. + if (bytecodeSection().lastTargetOffset().valid() && + off == bytecodeSection().lastTargetOffset() + + BytecodeOffsetDiff(JSOpLength_JumpTarget)) { + target->offset = bytecodeSection().lastTargetOffset(); + return true; + } + + target->offset = off; + bytecodeSection().setLastTargetOffset(off); + + BytecodeOffset opOff; + return emitJumpTargetOp(JSOp::JumpTarget, &opOff); +} + +bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { + BytecodeOffset offset; + if (!emitCheck(op, 5, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + MOZ_ASSERT(!jump->offset.valid() || + (0 <= jump->offset.value() && jump->offset < offset)); + jump->push(bytecodeSection().code(BytecodeOffset(0)), offset); + bytecodeSection().updateDepth(op, 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; +} + +void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { + MOZ_ASSERT( + !jump.offset.valid() || + (0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset())); + MOZ_ASSERT(0 <= target.offset.value() && + target.offset <= bytecodeSection().offset()); + MOZ_ASSERT_IF( + jump.offset.valid() && + target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(), + BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset)))); + jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target); +} + +bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { + if (!jump.offset.valid()) { + 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, unsigned count) { + MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth())); + MOZ_ASSERT(slotFromTop + 1 >= count); + + if (slotFromTop == 0 && count == 1) { + return emit1(JSOp::Dup); + } + + if (slotFromTop == 1 && count == 2) { + return emit1(JSOp::Dup2); + } + + if (slotFromTop >= Bit(24)) { + reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + + for (unsigned i = 0; i < count; i++) { + BytecodeOffset off; + if (!emitN(JSOp::DupAt, 3, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().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::Pop instructions (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::emitPickN(uint8_t n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Swap); + } + + return emit2(JSOp::Pick, n); +} + +bool BytecodeEmitter::emitUnpickN(uint8_t n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Swap); + } + + return emit2(JSOp::Unpick, n); +} + +bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { + return emit2(JSOp::CheckIsObj, uint8_t(kind)); +} + +bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) { + return emit2(JSOp::BuiltinObject, uint8_t(kind)); +} + +/* Updates line number notes, not column notes. */ +bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { + if (skipLocationSrcNotes()) { + return true; + } + + const ErrorReporter& er = errorReporter(); + std::optional onThisLineStatus = + er.isOnThisLine(offset, bytecodeSection().currentLine()); + if (!onThisLineStatus.has_value()) { + er.errorNoOffset(JSMSG_OUT_OF_MEMORY); + return false; + } + + bool onThisLine = *onThisLineStatus; + + if (!onThisLine) { + unsigned line = er.lineAt(offset); + unsigned delta = line - bytecodeSection().currentLine(); + + // If we use a `SetLine` note below, we want it to be relative to the + // scripts initial line number for better chance of sharing. + unsigned initialLine = sc->extent().lineno; + MOZ_ASSERT(line >= initialLine); + + /* + * Encode any change in the current source line number by using + * either several SrcNoteType::NewLine notes or just one + * SrcNoteType::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 + * SrcNoteType::SetLine. + */ + bytecodeSection().setCurrentLine(line, offset); + if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) { + if (!newSrcNote2(SrcNoteType::SetLine, + SrcNote::SetLine::toOperand(line, initialLine))) { + return false; + } + } else { + do { + if (!newSrcNote(SrcNoteType::NewLine)) { + return false; + } + } while (--delta != 0); + } + + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +/* Updates the line number and column number information in the source notes. */ +bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) { + if (skipLocationSrcNotes()) { + return true; + } + + if (!updateLineNumberNotes(offset)) { + return false; + } + + JS::LimitedColumnNumberOneOrigin columnIndex = + errorReporter().columnAt(offset); + + // Assert colspan is always representable. + static_assert((0 - ptrdiff_t(JS::LimitedColumnNumberOneOrigin::Limit)) >= + SrcNote::ColSpan::MinColSpan); + static_assert((ptrdiff_t(JS::LimitedColumnNumberOneOrigin::Limit) - 0) <= + SrcNote::ColSpan::MaxColSpan); + + JS::ColumnNumberOffset colspan = columnIndex - bytecodeSection().lastColumn(); + + if (colspan != JS::ColumnNumberOffset::zero()) { + if (lastLineOnlySrcNoteIndex != LastSrcNoteIsNotLineOnly) { + MOZ_ASSERT(bytecodeSection().lastColumn() == + JS::LimitedColumnNumberOneOrigin()); + + const SrcNotesVector& notes = bytecodeSection().notes(); + SrcNoteType type = notes[lastLineOnlySrcNoteIndex].type(); + if (type == SrcNoteType::NewLine) { + if (!convertLastNewLineToNewLineColumn(columnIndex)) { + return false; + } + } else { + MOZ_ASSERT(type == SrcNoteType::SetLine); + if (!convertLastSetLineToSetLineColumn(columnIndex)) { + return false; + } + } + } else { + if (!newSrcNote2(SrcNoteType::ColSpan, + SrcNote::ColSpan::toOperand(colspan))) { + return false; + } + } + bytecodeSection().setLastColumn(columnIndex, offset); + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +bool BytecodeEmitter::updateSourceCoordNotesIfNonLiteral(ParseNode* node) { + if (node->isLiteral()) { + return true; + } + return updateSourceCoordNotes(node->pn_pos.begin); +} + +uint32_t BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { + // Try to give the JSOp::LoopHead the same line number as the next + // instruction. nextpn is often a block, in which case the next instruction + // typically comes from the first statement inside. + if (nextpn->is()) { + nextpn = nextpn->as().scopeBody(); + } + if (nextpn->isKind(ParseNodeKind::StatementList)) { + if (ParseNode* firstStatement = nextpn->as().head()) { + nextpn = firstStatement; + } + } + + return nextpn->pn_pos.begin; +} + +bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { + MOZ_ASSERT(operand <= UINT16_MAX); + if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) { + BytecodeOffset off; + if (!emitN(op, 4, &off)) { + return false; + } + SET_UINT32(bytecodeSection().code(off), operand); + return true; +} + +bool BytecodeEmitter::emitGoto(NestableControl* target, GotoKind kind) { + NonLocalExitControl nle(this, kind == GotoKind::Continue + ? NonLocalExitKind::Continue + : NonLocalExitKind::Break); + return nle.emitNonLocalJump(target); +} + +AbstractScopePtr BytecodeEmitter::innermostScope() const { + return innermostEmitterScope()->scope(this); +} + +ScopeIndex BytecodeEmitter::innermostScopeIndex() const { + return *innermostEmitterScope()->scopeIndex(this); +} + +bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN; + MOZ_ASSERT(GetOpLength(op) == OpLength); + + BytecodeOffset offset; + if (!emitCheck(op, OpLength, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + SET_GCTHING_INDEX(code, index); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, TaggedParserAtomIndex atom) { + 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 != TaggedParserAtomIndex::WellKnown::dot_generator_()); + + GCThingIndex index; + if (!makeAtomIndex(atom, ParserAtom::Atomize::Yes, &index)) { + return false; + } + + return emitAtomOp(op, index); +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); +#ifdef DEBUG + auto atom = perScriptData().gcThingList().getAtom(atomIndex); + MOZ_ASSERT(compilationState.parserAtoms.isInstantiatedAsJSAtom(atom)); +#endif + return emitGCIndexOp(op, atomIndex); +} + +bool BytecodeEmitter::emitStringOp(JSOp op, TaggedParserAtomIndex atom) { + MOZ_ASSERT(atom); + GCThingIndex index; + if (!makeAtomIndex(atom, ParserAtom::Atomize::No, &index)) { + return false; + } + + return emitStringOp(op, index); +} + +bool BytecodeEmitter::emitStringOp(JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_STRING); + return emitGCIndexOp(op, atomIndex); +} + +bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitRegExp(GCThingIndex index) { + return emitGCIndexOp(JSOp::RegExp, index); +} + +bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { + MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); + MOZ_ASSERT(IsLocalOp(op)); + + BytecodeOffset off; + if (!emitN(op, LOCALNO_LEN, &off)) { + return false; + } + + SET_LOCALNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) { + MOZ_ASSERT(IsArgOp(op)); + BytecodeOffset off; + if (!emitN(op, ARGNO_LEN, &off)) { + return false; + } + + SET_ARGNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD || + JOF_OPTYPE(op) == JOF_DEBUGCOORD); + + constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN; + MOZ_ASSERT(GetOpLength(op) == 1 + N); + + BytecodeOffset off; + if (!emitN(op, N, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().code(off); + SET_ENVCOORD_HOPS(pc, ec.hops()); + pc += ENVCOORD_HOPS_LEN; + SET_ENVCOORD_SLOT(pc, ec.slot()); + pc += ENVCOORD_SLOT_LEN; + 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) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + +restart: + + switch (pn->getKind()) { + // Trivial cases with no side effects. + case ParseNodeKind::EmptyStmt: + case ParseNodeKind::TrueExpr: + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + 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::StringExpr: + case ParseNodeKind::TemplateStringExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::RegExpExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::NumberExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::BigIntExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + // |this| can throw in derived class constructors, including nested arrow + // functions or eval. + case ParseNodeKind::ThisExpr: + MOZ_ASSERT(pn->is()); + *answer = sc->needsThisTDZChecks(); + return true; + + // |new.target| doesn't have any side-effects. + case ParseNodeKind::NewTargetExpr: { + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + } + + // Trivial binary nodes with more token pos holders. + case ParseNodeKind::ImportMetaExpr: { + MOZ_ASSERT(pn->as().left()->isKind(ParseNodeKind::PosHolder)); + MOZ_ASSERT( + pn->as().right()->isKind(ParseNodeKind::PosHolder)); + *answer = false; + return true; + } + + case ParseNodeKind::BreakStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ContinueStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Watch out for getters! + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::DotExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Unary cases with side effects only if the child has them. + case ParseNodeKind::TypeOfExpr: + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + 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::TypeOfNameExpr: + 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::PreIncrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostDecrementExpr: + case ParseNodeKind::ThrowStmt: + 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::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + 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::YieldStarExpr: + case ParseNodeKind::YieldExpr: + case ParseNodeKind::AwaitExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Deletion generally has side effects, even if isolated cases have none. + case ParseNodeKind::DeleteNameExpr: + case ParseNodeKind::DeletePropExpr: + case ParseNodeKind::DeleteElemExpr: + case ParseNodeKind::DeleteOptionalChainExpr: + 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::ExpressionStmt: + return checkSideEffects(pn->as().kid(), answer); + + // Binary cases with obvious side effects. + case ParseNodeKind::InitExpr: + *answer = true; + return true; + + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: + 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 short circuit operators are well-behaved + // and perform no conversions. + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::OrExpr: + case ParseNodeKind::AndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::StrictNeExpr: + // Any subexpression of a comma expression could be effectful. + case ParseNodeKind::CommaExpr: + MOZ_ASSERT(!pn->as().empty()); + [[fallthrough]]; + // Subcomponents of a literal may be effectful. + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + for (ParseNode* item : pn->as().contents()) { + if (!checkSideEffects(item, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return true; + +#ifdef ENABLE_RECORD_TUPLE + case ParseNodeKind::RecordExpr: + case ParseNodeKind::TupleExpr: + MOZ_CRASH("Record and Tuple are not supported yet"); +#endif + +#ifdef ENABLE_DECORATORS + case ParseNodeKind::DecoratorList: + MOZ_CRASH("Decorators are not supported yet"); +#endif + + // 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::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::PrivateInExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + case ParseNodeKind::PowExpr: + MOZ_ASSERT(pn->as().count() >= 2); + *answer = true; + return true; + + case ParseNodeKind::PropertyDefinition: + 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::ElemExpr: + case ParseNodeKind::OptionalElemExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Throws if the operand is not of the right class. Can also call a private + // getter. + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: + *answer = true; + return true; + + // These affect visible names in this code, or in other code. + case ParseNodeKind::ImportDecl: + case ParseNodeKind::ExportFromStmt: + case ParseNodeKind::ExportDefaultStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Likewise. + case ParseNodeKind::ExportStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::CallImportExpr: + case ParseNodeKind::CallImportSpec: + 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::DoWhileStmt: + case ParseNodeKind::WhileStmt: + case ParseNodeKind::ForStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Declarations affect the name set of the relevant scope. + case ParseNodeKind::VarStmt: + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::IfStmt: + case ParseNodeKind::ConditionalExpr: { + 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::NewExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::SuperCallExpr: + 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::OptionalChain: + MOZ_ASSERT(pn->is()); + *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::ClassDecl: + 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::WithStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ReturnStmt: + 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::TryStmt: { + 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::SwitchStmt: { + SwitchStatement* switchStmt = &pn->as(); + if (!checkSideEffects(&switchStmt->discriminant(), answer)) { + return false; + } + return *answer || + checkSideEffects(&switchStmt->lexicalForCaseList(), answer); + } + + case ParseNodeKind::LabelStmt: + 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::TemplateStringListExpr: { + 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::DefaultConstructor: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassBodyScope: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl + case ParseNodeKind::StaticClassBlock: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import + case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import + case ParseNodeKind::ImportNamespaceSpec: // by ParseNodeKind::Import + case ParseNodeKind::ImportAttribute: // by ParseNodeKind::Import + case ParseNodeKind::ImportAttributeList: // by ParseNodeKind::Import + case ParseNodeKind::ImportModuleRequest: // by ParseNodeKind::Import + case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export + case ParseNodeKind::ExportNamespaceSpec: // 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::PropertyNameExpr: // by ParseNodeKind::Dot + MOZ_CRASH("handled by parent nodes"); + + case ParseNodeKind::LastUnused: + case ParseNodeKind::Limit: + MOZ_CRASH("invalid node kind"); + } + + MOZ_CRASH( + "invalid, unenumerated ParseNodeKind value encountered in " + "BytecodeEmitter::checkSideEffects"); +} + +bool BytecodeEmitter::isInLoop() { + return findInnermostNestableControl(); +} + +bool BytecodeEmitter::checkSingletonContext() { + MOZ_ASSERT_IF(sc->treatAsRunOnce(), sc->isTopLevelContext()); + return sc->treatAsRunOnce() && !isInLoop(); +} + +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; +} + +size_t BytecodeEmitter::countThisEnvironmentHops() { + unsigned numHops = 0; + + for (BytecodeEmitter* current = this; current; current = current->parent) { + for (EmitterScope* es = current->innermostEmitterScope(); es; + es = es->enclosingInFrame()) { + if (es->scope(current).is()) { + if (!es->scope(current).isArrow()) { + // The Parser is responsible for marking the environment as either + // closed-over or used-by-eval which ensure that is must exist. + MOZ_ASSERT(es->scope(current).hasEnvironment()); + return numHops; + } + } + if (es->scope(current).hasEnvironment()) { + numHops++; + } + } + } + + // The "this" environment exists outside of the compilation, but the + // `ScopeContext` recorded the number of additional hops needed, so add + // those in now. + MOZ_ASSERT(sc->allowSuperProperty()); + numHops += compilationState.scopeContext.enclosingThisEnvironmentHops; + return numHops; +} + +bool BytecodeEmitter::emitThisEnvironmentCallee() { + // Get the innermost enclosing function that has a |this| binding. + + // Directly load callee from the frame if possible. + if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) { + return emit1(JSOp::Callee); + } + + // We have to load the callee from the environment chain. + size_t numHops = countThisEnvironmentHops(); + + static_assert( + ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX, + "JSOp::EnvCallee operand size should match ENVCOORD_HOPS_LIMIT"); + + MOZ_ASSERT(numHops < ENVCOORD_HOPS_LIMIT - 1); + + return emit2(JSOp::EnvCallee, numHops); +} + +bool BytecodeEmitter::emitSuperBase() { + if (!emitThisEnvironmentCallee()) { + return false; + } + + return emit1(JSOp::SuperBase); +} + +void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, + &args); + + va_end(args); +} + +void BytecodeEmitter::reportError(uint32_t offset, unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, + &args); + + va_end(args); +} + +bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex) { + if (!writer.checkForDuplicatedNames(fc)) { + return false; + } + + size_t len = writer.getCode().size(); + auto* code = compilationState.alloc.newArrayUninitialized(len); + if (!code) { + js::ReportOutOfMemory(fc); + return false; + } + memcpy(code, writer.getCode().data(), len); + + ObjLiteralIndex objIndex(compilationState.objLiteralData.length()); + if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + if (!compilationState.objLiteralData.emplaceBack(code, len, writer.getKind(), + writer.getFlags(), + writer.getPropertyCount())) { + js::ReportOutOfMemory(fc); + return false; + } + + return perScriptData().gcThingList().append(objIndex, outIndex); +} + +bool BytecodeEmitter::emitPrepareIteratorResult() { + constexpr JSOp op = JSOp::NewObject; + + ObjLiteralWriter writer; + writer.beginShape(op); + + writer.setPropNameNoDuplicateCheck(parserAtoms(), + TaggedParserAtomIndex::WellKnown::value()); + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + writer.setPropNameNoDuplicateCheck(parserAtoms(), + TaggedParserAtomIndex::WellKnown::done()); + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + + GCThingIndex shape; + if (!addObjLiteralData(writer, &shape)) { + return false; + } + + return emitGCIndexOp(op, shape); +} + +bool BytecodeEmitter::emitFinishIteratorResult(bool done) { + if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::value())) { + return false; + } + if (!emit1(done ? JSOp::True : JSOp::False)) { + return false; + } + if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::done())) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitGetNameAtLocation(TaggedParserAtomIndex name, + const NameLocation& loc) { + NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitGetName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); + + return emitGetName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); + return emitGetPrivateName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(TaggedParserAtomIndex nameAtom) { + // The parser ensures the private name is present on the environment chain, + // but its location can be Dynamic or Global when emitting debugger + // eval-in-frame code. + NameLocation location = lookupName(nameAtom); + MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot || + location.kind() == NameLocation::Kind::EnvironmentCoordinate || + location.kind() == NameLocation::Kind::Dynamic || + location.kind() == NameLocation::Kind::Global); + + return emitGetNameAtLocation(nameAtom, location); +} + +bool BytecodeEmitter::emitTDZCheckIfNeeded(TaggedParserAtomIndex name, + const NameLocation& loc, + ValueIsOnStack isOnStack) { + // 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() || loc.isPrivateMethod() || loc.isSynthetic()); + + // Private names are implemented as lexical bindings, but it's just an + // implementation detail. Per spec there's no TDZ check when using them. + if (parserAtoms().isPrivateName(name)) { + return true; + } + + 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 the value is not on the stack, we have to load it first. + if (isOnStack == ValueIsOnStack::No) { + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::GetLocal, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::GetAliasedVar, loc.environmentCoordinate())) { + return false; + } + } + } + + // Emit the lexical check. + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::CheckLexical, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::CheckAliasedLexical, + loc.environmentCoordinate())) { + return false; + } + } + + // Pop the value if needed. + if (isOnStack == ValueIsOnStack::No) { + if (!emit1(JSOp::Pop)) { + 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) { + // Walk back up the list, emitting annotated name ops. + if (!emitAtomOp(JSOp::GetProp, pndot->key().atom())) { + 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, ValueUsage valueUsage) { + PropertyAccess* prop = &incDec->kid()->as(); + bool isSuper = prop->isSuper(); + ParseNodeKind kind = incDec->getKind(); + PropOpEmitter poe( + this, + kind == ParseNodeKind::PostIncrementExpr + ? PropOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? PropOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? 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)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitIncDec(prop->key().atom(), valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage) { + MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); + + ParseNodeKind kind = incDec->getKind(); + NameNode* name = &incDec->kid()->as(); + NameOpEmitter noe(this, name->atom(), + kind == ParseNodeKind::PostIncrementExpr + ? NameOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? NameOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? NameOpEmitter::Kind::PostDecrement + : NameOpEmitter::Kind::PreDecrement); + if (!noe.emitIncDec(valueUsage)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key, + ElemOpEmitter& eoe) { + if (exprOrSuper->isKind(ParseNodeKind::SuperBase)) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + UnaryNode* base = &exprOrSuper->as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(key)) { + // [stack] THIS KEY + return false; + } + + return true; + } + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + if (!emitTree(exprOrSuper)) { + // [stack] OBJ + return false; + } + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + if (!emitTree(key)) { + // [stack] OBJ? OBJ KEY + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemOpBase(JSOp op) { + if (!emit1(op)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe) { + MOZ_ASSERT(isSuper == elem->expression().isKind(ParseNodeKind::SuperBase)); + return emitObjAndKey(&elem->expression(), &elem->key(), eoe); +} + +static ElemOpEmitter::Kind ConvertIncDecKind(ParseNodeKind kind) { + switch (kind) { + case ParseNodeKind::PostIncrementExpr: + return ElemOpEmitter::Kind::PostIncrement; + case ParseNodeKind::PreIncrementExpr: + return ElemOpEmitter::Kind::PreIncrement; + case ParseNodeKind::PostDecrementExpr: + return ElemOpEmitter::Kind::PostDecrement; + case ParseNodeKind::PreDecrementExpr: + return ElemOpEmitter::Kind::PreDecrement; + default: + MOZ_CRASH("unexpected inc/dec node kind"); + } +} + +static PrivateOpEmitter::Kind PrivateConvertIncDecKind(ParseNodeKind kind) { + switch (kind) { + case ParseNodeKind::PostIncrementExpr: + return PrivateOpEmitter::Kind::PostIncrement; + case ParseNodeKind::PreIncrementExpr: + return PrivateOpEmitter::Kind::PreIncrement; + case ParseNodeKind::PostDecrementExpr: + return PrivateOpEmitter::Kind::PostDecrement; + case ParseNodeKind::PreDecrementExpr: + return PrivateOpEmitter::Kind::PreDecrement; + default: + MOZ_CRASH("unexpected inc/dec node kind"); + } +} + +bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage) { + PropertyByValue* elemExpr = &incDec->kid()->as(); + bool isSuper = elemExpr->isSuper(); + MOZ_ASSERT(!elemExpr->key().isKind(ParseNodeKind::PrivateName)); + ParseNodeKind kind = incDec->getKind(); + ElemOpEmitter eoe( + this, ConvertIncDecKind(kind), + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitIncDec(valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) { + MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrementExpr) || + incDec->isKind(ParseNodeKind::PostIncrementExpr) || + incDec->isKind(ParseNodeKind::PreDecrementExpr) || + incDec->isKind(ParseNodeKind::PostDecrementExpr)); + + ParseNode* call = incDec->kid(); + MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr)); + if (!emitTree(call)) { + // [stack] CALLRESULT + return false; + } + if (!emit1(JSOp::ToNumeric)) { + // [stack] N + return false; + } + + // The increment/decrement has no side effects, so proceed to throw for + // invalid assignment target. + return emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall)); +} + +bool BytecodeEmitter::emitPrivateIncDec(UnaryNode* incDec, + ValueUsage valueUsage) { + PrivateMemberAccess* privateExpr = &incDec->kid()->as(); + ParseNodeKind kind = incDec->getKind(); + PrivateOpEmitter xoe(this, PrivateConvertIncDecKind(kind), + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitIncDec(valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDouble(double d) { + BytecodeOffset offset; + if (!emitCheck(JSOp::Double, 9, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(JSOp::Double); + SET_INLINE_VALUE(code, DoubleValue(d)); + bytecodeSection().updateDepth(JSOp::Double, offset); + return true; +} + +bool BytecodeEmitter::emitNumberOp(double dval) { + int32_t ival; + if (NumberIsInt32(dval, &ival)) { + if (ival == 0) { + return emit1(JSOp::Zero); + } + if (ival == 1) { + return emit1(JSOp::One); + } + if ((int)(int8_t)ival == ival) { + return emit2(JSOp::Int8, uint8_t(int8_t(ival))); + } + + uint32_t u = uint32_t(ival); + if (u < Bit(16)) { + if (!emitUint16Operand(JSOp::Uint16, u)) { + return false; + } + } else if (u < Bit(24)) { + BytecodeOffset off; + if (!emitN(JSOp::Uint24, 3, &off)) { + return false; + } + SET_UINT24(bytecodeSection().code(off), u); + } else { + BytecodeOffset off; + if (!emitN(JSOp::Int32, 4, &off)) { + return false; + } + SET_INT32(bytecodeSection().code(off), ival); + } + return true; + } + + return emitDouble(dval); +} + +/* + * 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(switchStmt->discriminant().pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + 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 hasTopLevelFunctionDeclarations 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::NumberExpr) { + 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::NumberExpr)); + + 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::allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex) { + static constexpr uint32_t MaxResumeIndex = BitMask(24); + + static_assert( + MaxResumeIndex < uint32_t(AbstractGeneratorObject::RESUME_INDEX_RUNNING), + "resumeIndex should not include magic AbstractGeneratorObject " + "resumeIndex values"); + static_assert( + MaxResumeIndex <= INT32_MAX / sizeof(uintptr_t), + "resumeIndex * sizeof(uintptr_t) must fit in an int32. JIT code relies " + "on this when loading resume entries from BaselineScript"); + + *resumeIndex = bytecodeSection().resumeOffsetList().length(); + if (*resumeIndex > MaxResumeIndex) { + reportError(nullptr, JSMSG_TOO_MANY_RESUME_INDEXES); + return false; + } + + return bytecodeSection().resumeOffsetList().append(offset.value()); +} + +bool BytecodeEmitter::allocateResumeIndexRange( + mozilla::Span offsets, uint32_t* firstResumeIndex) { + *firstResumeIndex = 0; + + for (size_t i = 0, len = offsets.size(); i < len; i++) { + uint32_t resumeIndex; + if (!allocateResumeIndex(offsets[i], &resumeIndex)) { + return false; + } + if (i == 0) { + *firstResumeIndex = resumeIndex; + } + } + + return true; +} + +bool BytecodeEmitter::emitYieldOp(JSOp op) { + // ParseContext::Scope::setOwnStackSlotCount should check the fixed slot + // for the following, and it should prevent using fixed slot if there are + // too many bindings: + // * generator or asyn function + // * module code after top-level await + MOZ_ASSERT(innermostEmitterScopeNoCheck()->frameSlotEnd() <= + ParseContext::Scope::FixedSlotLimit); + + if (op == JSOp::FinalYieldRval) { + return emit1(JSOp::FinalYieldRval); + } + + MOZ_ASSERT(op == JSOp::InitialYield || op == JSOp::Yield || + op == JSOp::Await); + + BytecodeOffset off; + if (!emitN(op, 3, &off)) { + return false; + } + + if (op == JSOp::InitialYield || op == JSOp::Yield) { + bytecodeSection().addNumYields(); + } + + uint32_t resumeIndex; + if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { + return false; + } + + SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); + + BytecodeOffset unusedOffset; + return emitJumpTargetOp(JSOp::AfterYield, &unusedOffset); +} + +bool BytecodeEmitter::emitPushResumeKind(GeneratorResumeKind kind) { + return emit2(JSOp::ResumeKind, uint8_t(kind)); +} + +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)); + + auto name = 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()) { + // [stack] + return false; + } + + // Emit the new |this| value. + if (!emitTree(setThisNode->right())) { + // [stack] 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)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::CheckThisReinit)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEWTHIS + return false; + } + if (!noe.emitAssignment()) { + // [stack] NEWTHIS + return false; + } + + if (!emitInitializeInstanceMembers(true)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) { + MOZ_ASSERT(inPrologue()); + MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict())); + MOZ_ASSERT(body->is() || body->is()); + + if (body->is()) { + body = body->as().scopeBody(); + MOZ_ASSERT(body->is()); + } + + if (!body->as().hasTopLevelFunctionDeclarations()) { + return true; + } + + return emitHoistedFunctionsInList(&body->as()); +} + +// For Global and sloppy-Eval scripts, this performs most of the steps of the +// spec's [GlobalDeclarationInstantiation] and [EvalDeclarationInstantiation] +// operations. +// +// Note that while strict-Eval is handled in the same part of the spec, it never +// fails for global-redeclaration checks so those scripts initialize directly in +// their bytecode. +bool BytecodeEmitter::emitDeclarationInstantiation(ParseNode* body) { + if (sc->isModuleContext()) { + // ES Modules have dedicated variable and lexial environments and therefore + // do not have to perform redeclaration checks. We initialize their bindings + // elsewhere in bytecode. + return true; + } + + if (sc->isEvalContext() && sc->strict()) { + // Strict Eval has a dedicated variables (and lexical) environment and + // therefore does not have to perform redeclaration checks. We initialize + // their bindings elsewhere in the bytecode. + return true; + } + + // If we have no variables bindings, then we are done! + if (sc->isGlobalContext()) { + if (!sc->asGlobalContext()->bindings) { + return true; + } + } else { + MOZ_ASSERT(sc->isEvalContext()); + + if (!sc->asEvalContext()->bindings) { + return true; + } + } + +#if DEBUG + // There should be no emitted functions yet. + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope()); + } +#endif + + // Emit the hoisted functions to gc-things list. There is no bytecode + // generated yet to bind them. + if (!defineHoistedTopLevelFunctions(body)) { + return false; + } + + // Save the last GCThingIndex emitted. The hoisted functions are contained in + // the gc-things list up until this point. This set of gc-things also contain + // initial scopes (of which there must be at least one). + MOZ_ASSERT(perScriptData().gcThingList().length() > 0); + GCThingIndex lastFun = + GCThingIndex(perScriptData().gcThingList().length() - 1); + +#if DEBUG + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope() || + thing.isFunction()); + } +#endif + + // Check for declaration conflicts and initialize the bindings. + // NOTE: The self-hosting top-level script should not populate the builtins + // directly on the GlobalObject (and instead uses JSOp::GetIntrinsic for + // lookups). + if (emitterMode == BytecodeEmitter::EmitterMode::Normal) { + if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitScript(ParseNode* body) { + setScriptStartOffsetIfUnset(body->pn_pos.begin); + + MOZ_ASSERT(inPrologue()); + + TDZCheckCache tdzCache(this); + EmitterScope emitterScope(this); + Maybe topLevelAwait; + if (sc->isGlobalContext()) { + if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) { + return false; + } + } else if (sc->isEvalContext()) { + if (!emitterScope.enterEval(this, sc->asEvalContext())) { + return false; + } + } else { + MOZ_ASSERT(sc->isModuleContext()); + if (!emitterScope.enterModule(this, sc->asModuleContext())) { + return false; + } + if (sc->asModuleContext()->isAsync()) { + topLevelAwait.emplace(this); + } + } + + setFunctionBodyEndPos(body->pn_pos.end); + + bool isSloppyEval = sc->isEvalContext() && !sc->strict(); + if (isSloppyEval && body->is() && + !body->as().isEmptyScope()) { + // Sloppy eval scripts may emit hoisted functions bindings with a + // `JSOp::GlobalOrEvalDeclInstantiation` opcode below. If this eval needs a + // top-level lexical environment, we must ensure that environment is created + // before those functions are created and bound. + // + // This differs from the global-script case below because the global-lexical + // environment exists outside the script itself. In the case of strict eval + // scripts, the `emitterScope` above is already sufficient. + EmitterScope lexicalEmitterScope(this); + LexicalScopeNode* scope = &body->as(); + + if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, + scope->scopeBindings())) { + return false; + } + + if (!emitDeclarationInstantiation(scope->scopeBody())) { + return false; + } + + switchToMain(); + + ParseNode* scopeBody = scope->scopeBody(); + if (!emitLexicalScopeBody(scopeBody)) { + return false; + } + + if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) { + return false; + } + + if (!lexicalEmitterScope.leave(this)) { + return false; + } + } else { + if (!emitDeclarationInstantiation(body)) { + return false; + } + if (topLevelAwait) { + if (!topLevelAwait->prepareForModule()) { + return false; + } + } + + switchToMain(); + + if (topLevelAwait) { + if (!topLevelAwait->prepareForBody()) { + return false; + } + } + + if (!emitTree(body)) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(body->pn_pos.end)) { + return false; + } + } + + if (topLevelAwait) { + if (!topLevelAwait->emitEndModule()) { + return false; + } + } + + if (!markSimpleBreakpoint()) { + return false; + } + + if (!emitReturnRval()) { + return false; + } + + if (!emitterScope.leave(this)) { + return false; + } + + if (!NameFunctions(fc, parserAtoms(), body)) { + return false; + } + + // Create a Stencil and convert it into a JSScript. + return intoScriptStencil(CompilationStencil::TopLevelIndex); +} + +js::UniquePtr +BytecodeEmitter::createImmutableScriptData() { + uint32_t nslots; + if (!getNslots(&nslots)) { + return nullptr; + } + + bool isFunction = sc->isFunctionBox(); + uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0; + + mozilla::SaturateUint8 propertyCountEstimate = propertyAdditionEstimate; + + // Add fields to the property count estimate. + if (isFunction && sc->asFunctionBox()->useMemberInitializers()) { + propertyCountEstimate += + sc->asFunctionBox()->memberInitializers().numMemberInitializers; + } + + return ImmutableScriptData::new_( + fc, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex, + bytecodeSection().numICEntries(), isFunction, funLength, + propertyCountEstimate.value(), bytecodeSection().code(), + bytecodeSection().notes(), bytecodeSection().resumeOffsetList().span(), + bytecodeSection().scopeNoteList().span(), + bytecodeSection().tryNoteList().span()); +} + +#ifdef ENABLE_DECORATORS +bool BytecodeEmitter::emitCheckIsCallable() { + // This emits code to check if the value at the top of the stack is + // callable. The value is left on the stack. + // [stack] VAL + if (!emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::IsCallable())) { + // [stack] VAL ISCALLABLE + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] VAL ISCALLABLE UNDEFINED + return false; + } + if (!emitDupAt(2)) { + // [stack] VAL ISCALLABLE UNDEFINED VAL + return false; + } + return emitCall(JSOp::Call, 1); + // [stack] VAL ISCALLABLE_RESULT +} +#endif + +bool BytecodeEmitter::getNslots(uint32_t* nslots) { + uint64_t nslots64 = + maxFixedSlots + static_cast(bytecodeSection().maxStackDepth()); + if (nslots64 > UINT32_MAX) { + reportError(nullptr, JSMSG_NEED_DIET, "script"); + return false; + } + *nslots = nslots64; + return true; +} + +bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { + MOZ_ASSERT(inPrologue()); + ParamsBodyNode* paramsBody = funNode->body(); + FunctionBox* funbox = sc->asFunctionBox(); + + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); + + // [stack] + + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!emitTree(paramsBody->body())) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (funbox->index() == CompilationStencil::TopLevelIndex) { + if (!NameFunctions(fc, parserAtoms(), funNode)) { + return false; + } + } + + return fse.intoStencil(); +} + +bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, + size_t* emitted) { +#ifdef DEBUG + int depth = bytecodeSection().stackDepth(); +#endif + + switch (target->getKind()) { + case ParseNodeKind::Name: + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + // No need to recurse into ParseNodeKind::Array and ParseNodeKind::Object + // subpatterns here, since emitSetOrInitializeDestructuring does the + // recursion when setting or initializing the value. Getting reference + // doesn't recurse. + *emitted = 0; + break; + + case ParseNodeKind::DotExpr: { + 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)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + if (!poe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE + // [stack] # otherwise + // [stack] OBJ + return false; + } + + // SUPERBASE was pushed onto THIS in poe.prepareForRhs above. + *emitted = 1 + isSuper; + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + // SUPERBASE was pushed onto KEY in eoe.prepareForRhs above. + *emitted = 2 + isSuper; + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &target->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + *emitted = xoe.numReferenceSlots(); + break; + } + + case ParseNodeKind::CallExpr: + MOZ_ASSERT_UNREACHABLE( + "Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(bytecodeSection().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. + + switch (target->getKind()) { + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&target->as(), flav)) { + return false; + } + // emitDestructuringOps leaves the assigned (to-be-destructured) value on + // top of the stack. + break; + + case ParseNodeKind::Name: { + auto name = target->as().name(); + NameLocation loc = lookupName(name); + NameOpEmitter::Kind kind; + switch (flav) { + case DestructuringFlavor::Declaration: + kind = NameOpEmitter::Kind::Initialize; + break; + + case DestructuringFlavor::Assignment: + kind = NameOpEmitter::Kind::SimpleAssignment; + break; + } + + NameOpEmitter noe(this, name, loc, kind); + if (!noe.prepareForRhs()) { + // [stack] 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)) { + // [stack] ENV V + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + + break; + } + + case ParseNodeKind::DotExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS SUPERBASE VAL + // [stack] # otherwise + // [stack] OBJ VAL + PropertyAccess* prop = &target->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.skipObjAndRhs()) { + return false; + } + // [stack] # VAL + if (!poe.emitAssignment(prop->key().atom())) { + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS KEY SUPERBASE VAL + // [stack] # otherwise + // [stack] OBJ KEY VAL + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!eoe.skipObjAndKeyAndRhs()) { + return false; + } + if (!eoe.emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] OBJ NAME VAL + PrivateMemberAccess* privateExpr = &target->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!xoe.skipReference()) { + return false; + } + if (!xoe.emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + + case ParseNodeKind::CallExpr: + 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; +} + +JSOp BytecodeEmitter::getIterCallOp(JSOp callOp, + SelfHostedIter selfHostedIter) { + if (emitterMode == BytecodeEmitter::SelfHosting) { + MOZ_ASSERT(selfHostedIter != SelfHostedIter::Deny); + + switch (callOp) { + case JSOp::Call: + return JSOp::CallContent; + case JSOp::CallIter: + return JSOp::CallContentIter; + default: + MOZ_CRASH("Unknown iterator call op"); + } + } + + return callOp; +} + +bool BytecodeEmitter::emitIteratorNext( + const Maybe& callSourceCoordOffset, + IteratorKind iterKind /* = IteratorKind::Sync */, + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) { + MOZ_ASSERT(selfHostedIter != SelfHostedIter::Deny || + emitterMode != BytecodeEmitter::SelfHosting, + ".next() iteration is prohibited in self-hosted code because it" + "can run user-modifiable iteration code"); + + // [stack] ... NEXT ITER + MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); + + if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0, + callSourceCoordOffset)) { + // [stack] ... RESULT + return false; + } + + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] ... RESULT + return false; + } + } + + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] ... RESULT + return false; + } + return true; +} + +bool BytecodeEmitter::emitIteratorCloseInScope( + EmitterScope& currentScope, + IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) { + MOZ_ASSERT(selfHostedIter != SelfHostedIter::Deny || + emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because " + "it can run user-modifiable iteration code"); + + if (iterKind == IteratorKind::Sync) { + return emit2(JSOp::CloseIter, uint8_t(completionKind)); + } + + // Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and + // AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations. + // + // Callers need to ensure that the iterator object is at the top of the + // stack. + + // For non-Throw completions, we emit the equivalent of: + // + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // var innerResult = [Await] Call(returnMethod, iterator); + // CheckIsObj(innerResult); + // } + // + // Whereas for Throw completions, we emit: + // + // try { + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // [Await] Call(returnMethod, iterator); + // } + // } catch {} + + Maybe tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + + if (!tryCatch->emitTry()) { + // [stack] ... ITER + return false; + } + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... ITER ITER + return false; + } + + // Steps 1-2 are assertions, step 3 is implicit. + + // Step 4. + // + // Get the "return" method. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::return_())) { + // [stack] ... ITER RET + return false; + } + + // Step 5. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] ... ITER RET NULL-OR-UNDEF + return false; + } + + if (!ifReturnMethodIsDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] ... ITER RET + return false; + } + + // Steps 5.c, 7. + // + // Call the "return" method. + if (!emit1(JSOp::Swap)) { + // [stack] ... RET ITER + return false; + } + + if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0)) { + // [stack] ... RESULT + return false; + } + + // 7.4.7 AsyncIteratorClose, step 5.d. + if (iterKind == IteratorKind::Async) { + if (completionKind != CompletionKind::Throw) { + // Await clobbers rval, so save the current rval. + if (!emit1(JSOp::GetRval)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... RVAL RESULT + return false; + } + } + + if (!emitAwaitInScope(currentScope)) { + // [stack] ... RVAL? RESULT + return false; + } + + if (completionKind != CompletionKind::Throw) { + if (!emit1(JSOp::Swap)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::SetRval)) { + // [stack] ... RESULT + return false; + } + } + } + + // Step 6 (Handled in caller). + + // Step 8. + if (completionKind != CompletionKind::Throw) { + // Check that the "return" result is an object. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] ... RESULT + return false; + } + } + + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] ... ITER RET + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!ifReturnMethodIsDefined.emitEnd()) { + return false; + } + + if (completionKind == CompletionKind::Throw) { + if (!tryCatch->emitCatch()) { + // [stack] ... ITER EXC + return false; + } + + // Just ignore the exception thrown by call and await. + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!tryCatch->emitEnd()) { + // [stack] ... ITER + return false; + } + } + + // Step 9 (Handled in caller). + + return emit1(JSOp::Pop); + // [stack] ... +} + +template +bool BytecodeEmitter::wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter) { + MOZ_ASSERT(bytecodeSection().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::TryDestructuring)) { + return false; + } + + BytecodeOffset start = bytecodeSection().offset(); + if (!emitter(this)) { + return false; + } + BytecodeOffset end = bytecodeSection().offset(); + if (start != end) { + return addTryNote(TryNoteKind::Destructuring, iterDepth, start, end); + } + return true; +} + +bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] + return false; + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE + return false; + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + return true; +} + +bool BytecodeEmitter::emitAnonymousFunctionWithName( + ParseNode* node, TaggedParserAtomIndex name) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + if (!setFunName(node->as().funbox(), name)) { + return false; + } + + return emitTree(node); + } + + MOZ_ASSERT(node->is()); + + return emitClass(&node->as(), ClassNameKind::InferredName, name); +} + +bool BytecodeEmitter::emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + if (!emitTree(node)) { + // [stack] NAME FUN + return false; + } + if (!emitDupAt(1)) { + // [stack] NAME FUN NAME + return false; + } + if (!emit2(JSOp::SetFunName, uint8_t(prefixKind))) { + // [stack] NAME FUN + return false; + } + return true; + } + + MOZ_ASSERT(node->is()); + MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); + + return emitClass(&node->as(), ClassNameKind::ComputedName); +} + +bool BytecodeEmitter::setFunName(FunctionBox* funbox, + TaggedParserAtomIndex name) { + // 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 (funbox->hasInferredName()) { + MOZ_ASSERT(!funbox->emitBytecode); + MOZ_ASSERT(funbox->displayAtom() == name); + + return true; + } + + funbox->setInferredName(name); + return true; +} + +bool BytecodeEmitter::emitInitializer(ParseNode* initializer, + ParseNode* pattern) { + if (initializer->isDirectRHSAnonFunction()) { + MOZ_ASSERT(!pattern->isInParens()); + auto name = pattern->as().name(); + if (!emitAnonymousFunctionWithName(initializer, name)) { + return false; + } + } else { + if (!emitTree(initializer)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(getSelfHostedIterFor(pattern) == SelfHostedIter::Deny, + "array destructuring is prohibited in self-hosted code because it" + "can run user-modifiable iteration code"); + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr)); + MOZ_ASSERT(bytecodeSection().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 + // + // // NOTE: the fast path for this example is not applicable, because of + // // the spread and the assignment |c=y|, but it is documented here for a + // // simpler example, |let [a,b] = x;| + // // + // // if (IsOptimizableArray(x)) { + // // a = x[0]; + // // b = x[1]; + // // goto end: // (skip everything below) + // // } + // + // 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); + // + // end: + + bool isEligibleForArrayOptimizations = true; + for (ParseNode* member : pattern->contents()) { + switch (member->getKind()) { + case ParseNodeKind::Elision: + break; + case ParseNodeKind::Name: { + auto name = member->as().name(); + NameLocation loc = lookupName(name); + if (loc.kind() != NameLocation::Kind::ArgumentSlot && + loc.kind() != NameLocation::Kind::FrameSlot && + loc.kind() != NameLocation::Kind::EnvironmentCoordinate) { + isEligibleForArrayOptimizations = false; + } + break; + } + default: + // Unfortunately we can't handle any recursive destructuring, + // because we can't guarantee that the recursed-into parts + // won't run code which invalidates our constraints. We also + // cannot handle ParseNodeKind::AssignExpr for similar reasons. + isEligibleForArrayOptimizations = false; + break; + } + if (!isEligibleForArrayOptimizations) { + break; + } + } + + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ OBJ + return false; + } + + Maybe ifArrayOptimizable; + + if (isEligibleForArrayOptimizations) { + ifArrayOptimizable.emplace( + this, BranchEmitterBase::LexicalKind::MayContainLexicalAccessInBranch); + + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + + if (!emit1(JSOp::OptimizeGetIterator)) { + // [stack] OBJ OBJ IS_OPTIMIZABLE + return false; + } + + if (!ifArrayOptimizable->emitThenElse()) { + // [stack] OBJ OBJ + return false; + } + + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::length())) { + // [stack] OBJ LENGTH + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] LENGTH OBJ + return false; + } + + uint32_t idx = 0; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Elision)) { + idx += 1; + continue; + } + + if (!emit1(JSOp::Dup)) { + // [stack] LENGTH OBJ OBJ + return false; + } + + if (!emitNumberOp(idx)) { + // [stack] LENGTH OBJ OBJ IDX + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] LENGTH OBJ OBJ IDX IDX + return false; + } + + if (!emitDupAt(4)) { + // [stack] LENGTH OBJ OBJ IDX IDX LENGTH + return false; + } + + if (!emit1(JSOp::Lt)) { + // [stack] LENGTH OBJ OBJ IDX IS_IN_DENSE_BOUNDS + return false; + } + + InternalIfEmitter isInDenseBounds(this); + if (!isInDenseBounds.emitThenElse()) { + // [stack] LENGTH OBJ OBJ IDX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] LENGTH OBJ VALUE + return false; + } + + if (!isInDenseBounds.emitElse()) { + // [stack] LENGTH OBJ OBJ IDX + return false; + } + + if (!emitPopN(2)) { + // [stack] LENGTH OBJ + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] LENGTH OBJ UNDEFINED + return false; + } + + if (!isInDenseBounds.emitEnd()) { + // [stack] LENGTH OBJ VALUE|UNDEFINED + return false; + } + + if (!emitSetOrInitializeDestructuring(member, flav)) { + // [stack] LENGTH OBJ + return false; + } + + idx += 1; + } + + if (!emit1(JSOp::Swap)) { + // [stack] OBJ LENGTH + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] OBJ + return false; + } + + if (!ifArrayOptimizable->emitElse()) { + // [stack] OBJ OBJ + return false; + } + } + + if (!emitIterator(SelfHostedIter::Deny)) { + // [stack] ... 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)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + + if (!emitIteratorCloseInInnermostScope()) { + // [stack] ... OBJ + return false; + } + + if (ifArrayOptimizable.isSome()) { + if (!ifArrayOptimizable->emitEnd()) { + // [stack] OBJ + return false; + } + } + + return true; + } + + // Push an initial FALSE value for DONE. + if (!emit1(JSOp::False)) { + // [stack] ... OBJ NEXT ITER FALSE + return false; + } + + // TryNoteKind::Destructuring 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 = bytecodeSection().stackDepth(); + + for (ParseNode* member : pattern->contents()) { + bool isFirst = member == pattern->head(); + DebugOnly hasNext = !!member->pn_next; + + ParseNode* subpattern; + if (member->isKind(ParseNodeKind::Spread)) { + subpattern = member->as().kid(); + + MOZ_ASSERT(!subpattern->isKind(ParseNodeKind::AssignExpr)); + } else { + subpattern = member; + } + + ParseNode* lhsPattern = subpattern; + ParseNode* pndefault = nullptr; + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + lhsPattern = subpattern->as().left(); + pndefault = subpattern->as().right(); + } + + // Number of stack slots emitted for the LHS reference. + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + bool isElision = lhsPattern->isKind(ParseNodeKind::Elision); + if (!isElision) { + auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { + return bce->emitDestructuringLHSRef(lhsPattern, &emitted); + // [stack] ... OBJ NEXT ITER DONE LREF* + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitLHSRef)) { + return false; + } + } + + // Pick the DONE value to the top of the stack. + if (emitted) { + if (!emitPickN(emitted)) { + // [stack] ... 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)) { + // [stack] ... 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. + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifThenElse.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY + return false; + } + if (!ifThenElse.emitElse()) { + // [stack] ... 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, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER + return false; + } + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY + return false; + } + if (!emitNumberOp(0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread(SelfHostedIter::Deny)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... 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)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE + return false; + } + if (!emitUnpickN(emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY + return false; + } + + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); + // [stack] ... OBJ NEXT ITER TRUE + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + + MOZ_ASSERT(!hasNext); + break; + } + + InternalIfEmitter ifAlreadyDone(this); + if (!isFirst) { + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifAlreadyDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOp::True)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE + return false; + } + if (!emitUnpickN(emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF + return false; + } + + if (!ifAlreadyDone.emitElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + if (!emitDupAt(emitted + 1, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT + return false; + } + if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { + // [stack] ... OBJ NEXT ITER LREF* RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE + return false; + } + if (!emitUnpickN(emitted + 2)) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE + return false; + } + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE LREF* + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] ... 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); + // [stack] ... OBJ NEXT ITER DONE LREF* VALUE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitDefault)) { + return false; + } + } + + if (!isElision) { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); + // [stack] ... OBJ NEXT ITER DONE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + } else { + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE + return false; + } + } + } + + // The last DONE value is on top of the stack. If not DONE, call + // IteratorClose. + // [stack] ... OBJ NEXT ITER DONE + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + if (!emitIteratorCloseInInnermostScope()) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitEnd()) { + return false; + } + + if (ifArrayOptimizable.isSome()) { + if (!ifArrayOptimizable->emitEnd()) { + // [stack] OBJ + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) { + MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName)); + return emitTree(computedPropName->kid()) && emit1(JSOp::ToPropertyKey); +} + +bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + + // [stack] ... RHS + MOZ_ASSERT(bytecodeSection().stackDepth() > 0); + + if (!emit1(JSOp::CheckObjCoercible)) { + // [stack] ... RHS + return false; + } + + bool needsRestPropertyExcludedSet = + pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread); + if (needsRestPropertyExcludedSet) { + if (!emitDestructuringObjRestExclusionSet(pattern)) { + // [stack] ... RHS SET + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ... SET RHS + return false; + } + } + + for (ParseNode* member : pattern->contents()) { + ParseNode* subpattern; + if (member->isKind(ParseNodeKind::MutateProto) || + member->isKind(ParseNodeKind::Spread)) { + subpattern = member->as().kid(); + + MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), + !subpattern->isKind(ParseNodeKind::AssignExpr)); + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + subpattern = member->as().right(); + } + + ParseNode* lhs = subpattern; + ParseNode* pndefault = nullptr; + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + lhs = subpattern->as().left(); + pndefault = subpattern->as().right(); + } + + // Number of stack slots emitted for the LHS reference. + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + if (!emitDestructuringLHSRef(lhs, &emitted)) { + // [stack] ... SET? RHS LREF* + return false; + } + + // Duplicate the value being destructured to use as a reference base. + if (!emitDupAt(emitted)) { + // [stack] ... SET? RHS LREF* RHS + return false; + } + + if (member->isKind(ParseNodeKind::Spread)) { + if (!updateSourceCoordNotes(member->pn_pos.begin)) { + return false; + } + + if (!emit1(JSOp::NewInit)) { + // [stack] ... SET? RHS LREF* RHS TARGET + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... SET? RHS LREF* RHS TARGET TARGET + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] ... SET? RHS LREF* TARGET TARGET RHS + return false; + } + + if (needsRestPropertyExcludedSet) { + if (!emit2(JSOp::Pick, emitted + 4)) { + // [stack] ... RHS LREF* TARGET TARGET RHS SET + return false; + } + } + + CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered + : CopyOption::Unfiltered; + if (!emitCopyDataProperties(option)) { + // [stack] ... RHS LREF* TARGET + return false; + } + + // Destructure TARGET per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) { + // [stack] ... RHS + return false; + } + + MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); + break; + } + + // Now push the property value currently being matched, which is the value + // of the current property name "label" on the left of a colon in the object + // initialiser. + if (member->isKind(ParseNodeKind::MutateProto)) { + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::proto_())) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + if (!emitAtomOp(JSOp::GetProp, key->as().atom())) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } else { + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else { + // Otherwise this is a computed property name. BigInt keys are parsed + // as (synthetic) computed property names, too. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + if (!emitComputedPropertyName(&key->as())) { + // [stack] ... SET? RHS LREF* RHS KEY + return false; + } + + // Add the computed property key to the exclusion set. + if (needsRestPropertyExcludedSet) { + if (!emitDupAt(emitted + 3)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emitDupAt(1)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY UNDEFINED + return false; + } + if (!emit1(JSOp::InitElem)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... SET RHS LREF* RHS KEY + return false; + } + } + } + + // Get the property value. + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } + } + + if (pndefault) { + if (!emitDefault(pndefault, lhs)) { + // [stack] ... SET? RHS LREF* VALUE + return false; + } + } + + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) { + // [stack] ... SET? RHS + return false; + } + } + + return true; +} + +static bool IsDestructuringRestExclusionSetObjLiteralCompatible( + ListNode* pattern) { + uint32_t propCount = 0; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + propCount++; + + if (member->isKind(ParseNodeKind::MutateProto)) { + continue; + } + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + continue; + } + + // Number and BigInt keys aren't yet supported. Computed property names need + // to be added dynamically. + MOZ_ASSERT(key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr) || + key->isKind(ParseNodeKind::ComputedName)); + return false; + } + + if (propCount > SharedPropMap::MaxPropsForNonDictionary) { + // JSOp::NewObject cannot accept dictionary-mode objects. + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread)); + + // See if we can use ObjLiteral to construct the exclusion set object. + if (IsDestructuringRestExclusionSetObjLiteralCompatible(pattern)) { + if (!emitDestructuringRestExclusionSetObjLiteral(pattern)) { + // [stack] OBJ + return false; + } + } else { + // Take the slow but sure way and start off with a blank object. + if (!emit1(JSOp::NewInit)) { + // [stack] OBJ + return false; + } + } + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + TaggedParserAtomIndex pnatom; + if (member->isKind(ParseNodeKind::MutateProto)) { + pnatom = TaggedParserAtomIndex::WellKnown::proto_(); + } else { + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + pnatom = key->as().atom(); + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + return false; + } + } else { + // Otherwise this is a computed property name which needs to be added + // dynamically. BigInt keys are parsed as (synthetic) computed property + // names, too. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + continue; + } + } + + // Initialize elements with |undefined|. + if (!emit1(JSOp::Undefined)) { + return false; + } + + if (!pnatom) { + if (!emit1(JSOp::InitElem)) { + return false; + } + } else { + if (!emitAtomOp(JSOp::InitProp, pnatom)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav) { + if (pattern->isKind(ParseNodeKind::ArrayExpr)) { + 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::StringExpr || + item->getKind() == ParseNodeKind::TemplateStringExpr); + + // 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() == + TaggedParserAtomIndex::WellKnown::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 (!emitStringOp(JSOp::String, + TaggedParserAtomIndex::WellKnown::empty())) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeclarationList(ListNode* declList) { + for (ParseNode* decl : declList->contents()) { + ParseNode* pattern; + ParseNode* initializer; + if (decl->isKind(ParseNodeKind::Name)) { + pattern = decl; + initializer = nullptr; + } else { + AssignmentNode* assignNode = &decl->as(); + pattern = assignNode->left(); + initializer = assignNode->right(); + } + + if (pattern->isKind(ParseNodeKind::Name)) { + // initializer can be null here. + if (!emitSingleDeclaration(declList, &pattern->as(), + initializer)) { + return false; + } + } else { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) || + pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(initializer != nullptr); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(initializer)) { + return false; + } + + if (!emitDestructuringOps(&pattern->as(), + DestructuringFlavor::Declaration)) { + return false; + } + + if (!emit1(JSOp::Pop)) { + 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::VarStmt)) { + return true; + } + + auto nameAtom = decl->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ENV? + return false; + } + if (!initializer) { + // Lexical declarations are initialized to undefined without an + // initializer. + MOZ_ASSERT(declList->isKind(ParseNodeKind::LetDecl), + "var declarations without initializers handled above, " + "and const declarations must have initializers"); + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? UNDEF + return false; + } + } else { + MOZ_ASSERT(initializer); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitInitializer(initializer, decl)) { + // [stack] ENV? V + return false; + } + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAssignmentRhs( + ParseNode* rhs, TaggedParserAtomIndex anonFunctionName) { + if (rhs->isDirectRHSAnonFunction()) { + if (anonFunctionName) { + return emitAnonymousFunctionWithName(rhs, anonFunctionName); + } + return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None); + } + return emitTree(rhs); +} + +// The RHS value to assign is already on the stack, i.e., the next enumeration +// value in a for-in or for-of loop. Offset is the location in the stack of the +// already-emitted rhs. If we emitted a JSOp::BindName or JSOp::BindGName, then +// the scope is on the top of the stack and we need to dig one deeper to get +// the right RHS value. +bool BytecodeEmitter::emitAssignmentRhs(uint8_t offset) { + if (offset != 1) { + return emitPickN(offset - 1); + } + + return true; +} + +static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::InitExpr: + return JSOp::Nop; + case ParseNodeKind::AssignExpr: + return JSOp::Nop; + case ParseNodeKind::AddAssignExpr: + return JSOp::Add; + case ParseNodeKind::SubAssignExpr: + return JSOp::Sub; + case ParseNodeKind::BitOrAssignExpr: + return JSOp::BitOr; + case ParseNodeKind::BitXorAssignExpr: + return JSOp::BitXor; + case ParseNodeKind::BitAndAssignExpr: + return JSOp::BitAnd; + case ParseNodeKind::LshAssignExpr: + return JSOp::Lsh; + case ParseNodeKind::RshAssignExpr: + return JSOp::Rsh; + case ParseNodeKind::UrshAssignExpr: + return JSOp::Ursh; + case ParseNodeKind::MulAssignExpr: + return JSOp::Mul; + case ParseNodeKind::DivAssignExpr: + return JSOp::Div; + case ParseNodeKind::ModAssignExpr: + return JSOp::Mod; + case ParseNodeKind::PowAssignExpr: + return JSOp::Pow; + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + // Short-circuit assignment operators are handled elsewhere. + [[fallthrough]]; + default: + MOZ_CRASH("unexpected compound assignment op"); + } +} + +bool BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs) { + JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind); + bool isCompound = compoundOp != JSOp::Nop; + bool isInit = kind == ParseNodeKind::InitExpr; + + // We estimate the number of properties this could create + // if used as constructor merely by counting this.foo = assignment + // or init expressions; + // + // This currently doesn't handle this[x] = foo; + if (isInit || kind == ParseNodeKind::AssignExpr) { + if (lhs->isKind(ParseNodeKind::DotExpr)) { + if (lhs->as().expression().isKind( + ParseNodeKind::ThisExpr)) { + propertyAdditionEstimate++; + } + } + } + + MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) || + lhs->isKind(ParseNodeKind::ElemExpr) || + lhs->isKind(ParseNodeKind::PrivateMemberExpr)); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + TaggedParserAtomIndex name; + + Maybe noe; + Maybe poe; + Maybe eoe; + Maybe xoe; + + // Deal with non-name assignments. + uint8_t offset = 1; + + // Purpose of anonFunctionName: + // + // In normal name assignments (`f = function(){}`), an anonymous function gets + // an inferred name based on the left-hand side name node. + // + // In normal property assignments (`obj.x = function(){}`), the anonymous + // function does not have a computed name, and rhs->isDirectRHSAnonFunction() + // will be false (and anonFunctionName will not be used). However, in field + // initializers (`class C { x = function(){} }`), field initialization is + // implemented via a property or elem assignment (where we are now), and + // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the + // function. + TaggedParserAtomIndex anonFunctionName; + + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + anonFunctionName = name; + noe.emplace(this, name, + isCompound ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + poe.emplace(this, + isCompound ? PropOpEmitter::Kind::CompoundAssignment + : isInit ? PropOpEmitter::Kind::PropInit + : PropOpEmitter::Kind::SimpleAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe->prepareForObj()) { + return false; + } + anonFunctionName = prop->name(); + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + // SUPERBASE is pushed onto THIS later in poe->emitGet below. + offset += 2; + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + offset += 1; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + eoe.emplace(this, + isCompound ? ElemOpEmitter::Kind::CompoundAssignment + : isInit ? ElemOpEmitter::Kind::PropInit + : ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe->emitGet below. + offset += 3; + } else { + offset += 2; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &lhs->as(); + xoe.emplace(this, + isCompound ? PrivateOpEmitter::Kind::CompoundAssignment + : isInit ? PrivateOpEmitter::Kind::PropInit + : PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe->emitReference()) { + // [stack] OBJ KEY + return false; + } + offset += xoe->numReferenceSlots(); + break; + } + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + break; + case ParseNodeKind::CallExpr: + if (!emitTree(lhs)) { + return false; + } + + // Assignment to function calls is forbidden, but we have to make the + // call first. Now we can throw. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall))) { + 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::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE PROP + // [stack] # otherwise + // [stack] OBJ PROP + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + if (!eoe->emitGet()) { + // [stack] KEY THIS OBJ ELEM + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: { + if (!xoe->emitGet()) { + // [stack] OBJ KEY VALUE + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // 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)) { + // [stack] NULL + return false; + } + break; + default:; + } + } + + switch (lhs->getKind()) { + case ParseNodeKind::Name: + if (!noe->prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + offset += noe->emittedBindOp(); + break; + case ParseNodeKind::DotExpr: + if (!poe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ + // [stack] # if Compound Assignment with Super + // [stack] THIS SUPERBASE PROP + // [stack] # if Compound Assignment with other + // [stack] OBJ PROP + return false; + } + break; + case ParseNodeKind::ElemExpr: + if (!eoe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS KEY SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ KEY + // [stack] # if Compound Assignment with Super + // [stack] THIS KEY SUPERBASE ELEM + // [stack] # if Compound Assignment with other + // [stack] OBJ KEY ELEM + return false; + } + break; + case ParseNodeKind::PrivateMemberExpr: + // no stack adjustment needed + break; + default: + break; + } + + if (rhs) { + if (!emitAssignmentRhs(rhs, anonFunctionName)) { + // [stack] ... VAL? RHS + return false; + } + } else { + // Assumption: Things with pre-emitted RHS values never need to be named. + if (!emitAssignmentRhs(offset)) { + // [stack] ... VAL? RHS + return false; + } + } + + /* If += etc., emit the binary operator with a hint for the decompiler. */ + if (isCompound) { + if (!emit1(compoundOp)) { + // [stack] ... VAL + return false; + } + if (!emit1(JSOp::NopIsAssignOp)) { + // [stack] ... VAL + return false; + } + } + + /* Finally, emit the specialized assignment bytecode. */ + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // We threw above, so nothing to do here. + break; + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: + if (!xoe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&lhs->as(), + DestructuringFlavor::Assignment)) { + return false; + } + break; + default: + MOZ_ASSERT(0); + } + return true; +} + +bool BytecodeEmitter::emitShortCircuitAssignment(AssignmentNode* node) { + TDZCheckCache tdzCache(this); + + JSOp op; + switch (node->getKind()) { + case ParseNodeKind::CoalesceAssignExpr: + op = JSOp::Coalesce; + break; + case ParseNodeKind::OrAssignExpr: + op = JSOp::Or; + break; + case ParseNodeKind::AndAssignExpr: + op = JSOp::And; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + ParseNode* lhs = node->left(); + ParseNode* rhs = node->right(); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + TaggedParserAtomIndex name; + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + Maybe xoe; + + int32_t depth = bytecodeSection().stackDepth(); + + // Number of values pushed onto the stack in addition to the lhs value. + int32_t numPushed; + + // Evaluate the left-hand side expression and compute any stack values needed + // for the assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); + + if (!noe->prepareForRhs()) { + // [stack] ENV? LHS + return false; + } + + numPushed = noe->emittedBindOp(); + break; + } + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + + poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + if (!poe->prepareForObj()) { + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + if (!poe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + numPushed = 1 + isSuper; + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + if (!eoe->emitGet()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + if (!eoe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + numPushed = 2 + isSuper; + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &lhs->as(); + xoe.emplace(this, PrivateOpEmitter::Kind::CompoundAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe->emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe->emitGet()) { + // [stack] OBJ NAME LHS + return false; + } + numPushed = xoe->numReferenceSlots(); + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + numPushed + 1); + + // Test for the short-circuit condition. + JumpList jump; + if (!emitJump(op, &jump)) { + // [stack] ... LHS + return false; + } + + // The short-circuit condition wasn't fulfilled, pop the left-hand side value + // which was kept on the stack. + if (!emit1(JSOp::Pop)) { + // [stack] ... + return false; + } + + if (!emitAssignmentRhs(rhs, name)) { + // [stack] ... RHS + return false; + } + + // Perform the actual assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: + if (!xoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); + + // Join with the short-circuit jump and pop anything left on the stack. + if (numPushed > 0) { + JumpList jumpAroundPop; + if (!emitJump(JSOp::Goto, &jumpAroundPop)) { + // [stack] RHS + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + // [stack] ... LHS + return false; + } + + // Reconstruct the stack depth after the jump. + bytecodeSection().setStackDepth(depth + 1 + numPushed); + + // Move the left-hand side value to the bottom and pop the rest. + if (!emitUnpickN(numPushed)) { + // [stack] LHS ... + return false; + } + if (!emitPopN(numPushed)) { + // [stack] LHS + return false; + } + + if (!emitJumpTargetAndPatch(jumpAroundPop)) { + // [stack] LHS | RHS + return false; + } + } else { + if (!emitJumpTargetAndPatch(jump)) { + // [stack] LHS | RHS + return false; + } + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); + + return true; +} + +bool BytecodeEmitter::emitCallSiteObjectArray(ObjLiteralWriter& writer, + ListNode* cookedOrRaw, + ParseNode* head, uint32_t count) { + DebugOnly idx = 0; + for (ParseNode* pn : cookedOrRaw->contentsFrom(head)) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) || + pn->isKind(ParseNodeKind::RawUndefinedExpr)); + + if (!emitObjLiteralValue(writer, pn)) { + return false; + } + idx++; + } + MOZ_ASSERT(idx == count); + + return true; +} + +bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) { + constexpr JSOp op = JSOp::CallSiteObj; + + // The first element of a call-site node is the raw-values list. Skip over it. + ListNode* raw = callSiteObj->rawNodes(); + MOZ_ASSERT(raw->isKind(ParseNodeKind::ArrayExpr)); + ParseNode* head = callSiteObj->head()->pn_next; + + uint32_t count = callSiteObj->count() - 1; + MOZ_ASSERT(count == raw->count()); + + ObjLiteralWriter writer; + writer.beginCallSiteObj(op); + writer.beginDenseArrayElements(); + + // Write elements of the two arrays: the 'cooked' values followed by the + // 'raw' values. + MOZ_RELEASE_ASSERT(count < UINT32_MAX / 2, + "Number of elements for both arrays must fit in uint32_t"); + if (!emitCallSiteObjectArray(writer, callSiteObj, head, count)) { + return false; + } + if (!emitCallSiteObjectArray(writer, raw, raw->head(), count)) { + return false; + } + + GCThingIndex cookedIndex; + if (!addObjLiteralData(writer, &cookedIndex)) { + return false; + } + + MOZ_ASSERT(sc->hasCallSiteObj()); + + return emitInternedObjectOp(cookedIndex, op); +} + +bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { + // We must be nested under a try-finally statement. + MOZ_ASSERT(innermostNestableControl->is()); + + 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::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(¶m->as(), + DestructuringFlavor::Declaration)) { + 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: + // goto + // [jump target for returning from finally] + // 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; +} + +[[nodiscard]] bool BytecodeEmitter::emitJumpToFinally(JumpList* jump, + uint32_t idx) { + // Push the continuation index. + if (!emitNumberOp(idx)) { + return false; + } + + // Push |exception_stack|. + if (!emit1(JSOp::Null)) { + return false; + } + + // Push |throwing|. + if (!emit1(JSOp::False)) { + return false; + } + + // Jump to the finally block. + if (!emitJumpNoFallthrough(JSOp::Goto, jump)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitIf(TernaryNode* ifNode) { + IfEmitter ifThenElse(this); + + if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) { + return false; + } + +if_again: + ParseNode* testNode = ifNode->kid1(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (testNode->isKind(ParseNodeKind::NotExpr)) { + testNode = testNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + if (!markStepBreakpoint()) { + return false; + } + + // Emit code for the condition before pushing stmtInfo. + // NOTE: NotExpr of testNode may be unwrapped, and in that case the negation + // is handled by conditionKind. + if (!emitTree(testNode)) { + return false; + } + + ParseNode* elseNode = ifNode->kid3(); + if (elseNode) { + if (!ifThenElse.emitThenElse(conditionKind)) { + return false; + } + } else { + if (!ifThenElse.emitThen(conditionKind)) { + return false; + } + } + + /* Emit code for the then part. */ + if (!emitTree(ifNode->kid2())) { + return false; + } + + if (elseNode) { + if (elseNode->isKind(ParseNodeKind::IfStmt)) { + ifNode = &elseNode->as(); + + if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->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()); + + // We can call this multiple times for sloppy eval scopes. + if (stmtList->emittedTopLevelFunctionDeclarations()) { + return true; + } + + stmtList->setEmittedTopLevelFunctionDeclarations(); + + for (ParseNode* stmt : stmtList->contents()) { + ParseNode* maybeFun = stmt; + + if (!sc->strict()) { + while (maybeFun->isKind(ParseNodeKind::LabelStmt)) { + maybeFun = maybeFun->as().statement(); + } + } + + if (maybeFun->is() && + maybeFun->as().functionIsHoisted()) { + if (!emitTree(maybeFun)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + 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 or emitScript. + 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) { + LexicalScopeEmitter lse(this); + + ParseNode* body = lexicalScope->scopeBody(); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) { + return false; + } + + if (!emitLexicalScopeBody(body)) { + return false; + } + + if (!lse.emitEnd()) { + return false; + } + + return true; + } + + // 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; + } + } + + 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 = lexicalScope->kind(); + } + + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) { + return false; + } + + if (body->isKind(ParseNodeKind::ForStmt)) { + // for loops need to emit JSOp::FreshenLexicalEnv/JSOp::RecreateLexicalEnv + // if there are lexical declarations in the head. Signal this by passing a + // non-nullptr lexical scope. + if (!emitFor(&body->as(), &lse.emitterScope())) { + return false; + } + } else { + if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) { + return false; + } + } + + if (!lse.emitEnd()) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitWith(BinaryNode* withNode) { + // Ensure that the column of the 'with' is set properly. + if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + 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 = bytecodeSection().stackDepth(); + + uint32_t argc; + if (option == CopyOption::Filtered) { + MOZ_ASSERT(depth > 2); + // [stack] TARGET SOURCE SET + argc = 3; + + if (!emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::CopyDataProperties())) { + // [stack] TARGET SOURCE SET COPYDATAPROPERTIES + return false; + } + } else { + MOZ_ASSERT(depth > 1); + // [stack] TARGET SOURCE + argc = 2; + + if (!emitAtomOp( + JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::CopyDataPropertiesUnfiltered())) { + // [stack] TARGET SOURCE COPYDATAPROPERTIES + return false; + } + } + + if (!emit1(JSOp::Undefined)) { + // [stack] TARGET SOURCE SET? COPYDATAPROPERTIES + // UNDEFINED + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED + // TARGET + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET + // SOURCE + return false; + } + if (option == CopyOption::Filtered) { + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET + return false; + } + } + // Callee is always self-hosted instrinsic, and cannot be content function. + if (!emitCall(JSOp::CallIgnoresRv, argc)) { + // [stack] IGNORED + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth()); + return true; +} + +bool BytecodeEmitter::emitBigIntOp(BigIntLiteral* bigint) { + GCThingIndex index; + if (!perScriptData().gcThingList().append(bigint, &index)) { + return false; + } + return emitGCIndexOp(JSOp::BigInt, index); +} + +bool BytecodeEmitter::emitIterable(ParseNode* value, + SelfHostedIter selfHostedIter, + IteratorKind iterKind) { + MOZ_ASSERT(getSelfHostedIterFor(value) == selfHostedIter); + + if (!emitTree(value)) { + // [stack] ITERABLE + return false; + } + + switch (selfHostedIter) { + case SelfHostedIter::Deny: + case SelfHostedIter::AllowContent: + // [stack] ITERABLE + return true; + + case SelfHostedIter::AllowContentWith: { + // This is the following case: + // + // for (const nextValue of allowContentIterWith(items, usingIterator)) { + // + // `items` is emitted by `emitTree(value)` above, and the result is on the + // stack as ITERABLE. + // `usingIterator` is the value of `items[Symbol.iterator]`, that's + // already retrieved. + ListNode* argsList = value->as().args(); + MOZ_ASSERT_IF(iterKind == IteratorKind::Sync, argsList->count() == 2); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, argsList->count() == 3); + + if (!emitTree(argsList->head()->pn_next)) { + // [stack] ITERABLE ITERFN + return false; + } + + // Async iterator has two possible iterators: An async iterator and a sync + // iterator. + if (iterKind == IteratorKind::Async) { + if (!emitTree(argsList->head()->pn_next->pn_next)) { + // [stack] ITERABLE ASYNC_ITERFN SYNC_ITERFN + return false; + } + } + + // [stack] ITERABLE ASYNC_ITERFN? SYNC_ITERFN + return true; + } + + case SelfHostedIter::AllowContentWithNext: { + // This is the following case: + // + // for (const nextValue of allowContentIterWithNext(iterator, next)) { + // + // `iterator` is emitted by `emitTree(value)` above, and the result is on + // the stack as ITER. + // `next` is the value of `iterator.next`, that's already retrieved. + ListNode* argsList = value->as().args(); + MOZ_ASSERT(argsList->count() == 2); + + if (!emitTree(argsList->head()->pn_next)) { + // [stack] ITER NEXT + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + + // [stack] NEXT ITER + return true; + } + } + + MOZ_CRASH("invalid self-hosted iteration kind"); +} + +bool BytecodeEmitter::emitIterator(SelfHostedIter selfHostedIter) { + MOZ_ASSERT(selfHostedIter != SelfHostedIter::Deny || + emitterMode != BytecodeEmitter::SelfHosting, + "[Symbol.iterator]() call is prohibited in self-hosted code " + "because it can run user-modifiable iteration code"); + + if (selfHostedIter == SelfHostedIter::AllowContentWithNext) { + // [stack] NEXT ITER + + // Nothing to do, stack already contains the iterator and its `next` method. + return true; + } + + if (selfHostedIter != SelfHostedIter::AllowContentWith) { + // [stack] OBJ + + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ITERFN + return false; + } + } + + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + return true; +} + +bool BytecodeEmitter::emitAsyncIterator(SelfHostedIter selfHostedIter) { + MOZ_ASSERT(selfHostedIter != SelfHostedIter::AllowContentWithNext); + MOZ_ASSERT(selfHostedIter != SelfHostedIter::Deny || + emitterMode != BytecodeEmitter::SelfHosting, + "[Symbol.asyncIterator]() call is prohibited in self-hosted code " + "because it can run user-modifiable iteration code"); + + if (selfHostedIter != SelfHostedIter::AllowContentWith) { + // [stack] OBJ + + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::asyncIterator))) { + // [stack] OBJ OBJ @@ASYNCITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ASYNC_ITERFN + return false; + } + } else { + // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN + + if (!emitElemOpBase(JSOp::Swap)) { + // [stack] OBJ SYNC_ITERFN ASYNC_ITERFN + return false; + } + } + + InternalIfEmitter ifAsyncIterIsUndefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN NULL-OR-UNDEF + return false; + } + if (!ifAsyncIterIsUndefined.emitThenElse()) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] OBJ SYNC_ITERFN? + return false; + } + + if (selfHostedIter != SelfHostedIter::AllowContentWith) { + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ SYNC_ITERFN + return false; + } + } else { + // [stack] OBJ SYNC_ITERFN + } + + if (!emit1(JSOp::Swap)) { + // [stack] SYNC_ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER SYNCNEXT + return false; + } + + if (!emit1(JSOp::ToAsyncIter)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitElse()) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN + return false; + } + + if (selfHostedIter == SelfHostedIter::AllowContentWith) { + if (!emit1(JSOp::Swap)) { + // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] OBJ ASYNC_ITERFN + return false; + } + } + + if (!emit1(JSOp::Swap)) { + // [stack] ASYNC_ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetAsyncIterator)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitEnd()) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter) { + // [stack] NEXT ITER ARR I + return emitSpread(selfHostedIter, 2, JSOp::InitElemInc); + // [stack] ARR FINAL_INDEX +} + +bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter, + int spreadeeStackItems, JSOp storeElementOp) { + LoopControl loopInfo(this, StatementKind::Spread); + // In the [stack] annotations, (spreadee) can be "ARR I" (when spreading + // into an array or into call parameters, or "TUPLE" (when spreading into a + // tuple) + + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER (spreadee) + return false; + } + + { +#ifdef DEBUG + auto loopDepth = bytecodeSection().stackDepth(); +#endif + + // Spread operations can't contain |continue|, so don't bother setting loop + // and enclosing "update" offsets, as we do with for-loops. + + if (!emitDupAt(spreadeeStackItems + 1, 2)) { + // [stack] NEXT ITER (spreadee) NEXT ITER + return false; + } + if (!emitIteratorNext(Nothing(), IteratorKind::Sync, selfHostedIter)) { + // [stack] NEXT ITER (spreadee) RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER (spreadee) RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER (spreadee) RESULT DONE + return false; + } + if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) { + // [stack] NEXT ITER (spreadee) RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER (spreadee) VALUE + return false; + } + if (!emit1(storeElementOp)) { + // [stack] NEXT ITER (spreadee) + return false; + } + + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER (spreadee) + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == loopDepth); + } + + // When we leave the loop body and jump to this point, the result value is + // still on the stack. Account for that by updating the stack depth + // manually. + bytecodeSection().setStackDepth(bytecodeSection().stackDepth() + 1); + + // No continues should occur in spreads. + MOZ_ASSERT(!loopInfo.continues.offset.valid()); + + if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) { + // [stack] ITER (spreadee) RESULT NEXT + return false; + } + if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) { + // [stack] (spreadee) RESULT NEXT ITER + return false; + } + + return emitPopN(3); + // [stack] (spreadee) +} + +bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) { + MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || + forHead->isKind(ParseNodeKind::ForOf)); + + MOZ_ASSERT(bytecodeSection().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 (!target->is()) { + return emitAssignmentOrInit(ParseNodeKind::AssignExpr, target, nullptr); + // [stack] ... 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. + + auto* declarationList = &target->as(); + if (!updateSourceCoordNotes(declarationList->pn_pos.begin)) { + return false; + } + + target = declarationList->singleBinding(); + + NameNode* nameNode = nullptr; + if (target->isKind(ParseNodeKind::Name)) { + nameNode = &target->as(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + BinaryNode* assignNode = &target->as(); + if (assignNode->left()->is()) { + nameNode = &assignNode->left()->as(); + } + } + + if (nameNode) { + auto nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, 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(bytecodeSection().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(bytecodeSection().stackDepth() >= 1); + } + if (!noe.emitAssignment()) { + return false; + } + + // The caller handles removing the iteration value from the stack. + return true; + } + + MOZ_ASSERT( + !target->isKind(ParseNodeKind::AssignExpr), + "for-in/of loop destructuring declarations can't have initializers"); + + MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)); + return emitDestructuringOps(&target->as(), + DestructuringFlavor::Declaration); +} + +bool BytecodeEmitter::emitForOf(ForNode* forOfLoop, + const EmitterScope* headLexicalEmitterScope) { + MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::ForStmt)); + + 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->isSuspendableContext()); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, + sc->asSuspendableContext()->isAsync()); + + ParseNode* forHeadExpr = forOfHead->kid3(); + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + auto selfHostedIter = getSelfHostedIterFor(forHeadExpr); + ForOfEmitter forOf(this, headLexicalEmitterScope, selfHostedIter, iterKind); + + if (!forOf.emitIterated()) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitIterable(forHeadExpr, selfHostedIter, iterKind)) { + // [stack] ITERABLE + return false; + } + + if (headLexicalEmitterScope) { + DebugOnly forOfTarget = forOfHead->kid1(); + MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) || + forOfTarget->isKind(ParseNodeKind::ConstDecl)); + } + + if (!forOf.emitInitialize(forOfHead->pn_pos.begin)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!emitInitializeForInOrOfTarget(forOfHead)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!forOf.emitBody()) { + // [stack] NEXT ITER UNDEF + return false; + } + + // Perform the loop body. + ParseNode* forBody = forOfLoop->body(); + if (!emitTree(forBody)) { + // [stack] NEXT ITER UNDEF + return false; + } + + if (!forOf.emitEnd(forHeadExpr->pn_pos.begin)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitForIn(ForNode* forInLoop, + const EmitterScope* headLexicalEmitterScope) { + 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 (forInTarget->is()) { + auto* declarationList = &forInTarget->as(); + + ParseNode* decl = declarationList->singleBinding(); + if (decl->isKind(ParseNodeKind::AssignExpr)) { + BinaryNode* assignNode = &decl->as(); + if (assignNode->left()->is()) { + NameNode* nameNode = &assignNode->left()->as(); + ParseNode* initializer = assignNode->right(); + MOZ_ASSERT( + forInTarget->isKind(ParseNodeKind::VarStmt), + "for-in initializers are only permitted for |var| declarations"); + + if (!updateSourceCoordNotes(decl->pn_pos.begin)) { + return false; + } + + auto nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (!emitInitializer(initializer, nameNode)) { + return false; + } + if (!noe.emitAssignment()) { + return false; + } + + // Pop the initializer. + if (!emit1(JSOp::Pop)) { + return false; + } + } + } + } + + if (!forIn.emitIterated()) { + // [stack] + return false; + } + + // Evaluate the expression being iterated. + ParseNode* expr = forInHead->kid3(); + + if (!updateSourceCoordNotes(expr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr)) { + // [stack] EXPR + return false; + } + + MOZ_ASSERT(forInLoop->iflags() == 0); + + MOZ_ASSERT_IF(headLexicalEmitterScope, + forInTarget->isKind(ParseNodeKind::LetDecl) || + forInTarget->isKind(ParseNodeKind::ConstDecl)); + + if (!forIn.emitInitialize()) { + // [stack] ITER ITERVAL + return false; + } + + if (!emitInitializeForInOrOfTarget(forInHead)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitBody()) { + // [stack] ITER ITERVAL + return false; + } + + // Perform the loop body. + ParseNode* forBody = forInLoop->body(); + if (!emitTree(forBody)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitEnd(forInHead->pn_pos.begin)) { + // [stack] + 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::LetDecl); + + CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr); + + if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) { + // [stack] + 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->is()) { + MOZ_ASSERT(!init->as().empty()); + + if (!emitTree(init)) { + // [stack] + return false; + } + } else { + if (!updateSourceCoordNotes(init->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emitTree(init, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + } + + if (!cfor.emitCond(cond ? Some(cond->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + if (cond) { + if (!updateSourceCoordNotes(cond->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(cond)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitBody(cond ? CForEmitter::Cond::Present + : CForEmitter::Cond::Missing)) { + // [stack] + return false; + } + + if (!emitTree(forBody)) { + // [stack] + return false; + } + + if (!cfor.emitUpdate( + update ? CForEmitter::Update::Present : CForEmitter::Update::Missing, + update ? Some(update->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + // Check for update code to do before the condition (if any). + if (update) { + if (!updateSourceCoordNotes(update->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(update, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitEnd(forNode->pn_pos.begin)) { + // [stack] + 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( + FunctionNode* funNode, bool needsProto /* = false */) { + FunctionBox* funbox = funNode->funbox(); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted() + ? FunctionEmitter::IsHoisted::Yes + : FunctionEmitter::IsHoisted::No); + + // |wasEmittedByEnclosingScript| flag is set to true 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->wasEmittedByEnclosingScript()) { + if (!fe.emitAgain()) { + // [stack] + return false; + } + MOZ_ASSERT(funNode->functionIsHoisted()); + } else if (funbox->isInterpreted()) { + if (!funbox->emitBytecode) { + return fe.emitLazy(); + // [stack] FUN? + } + + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, funbox); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? + return false; + } + } else { + if (!fe.emitAsmJSModule()) { + // [stack] + return false; + } + } + + // Track the last emitted top-level self-hosted function, so that intrinsics + // can adjust attributes at parse time. + // + // NOTE: We also disallow lambda functions in the top-level body. This is done + // to simplify handling of the self-hosted stencil. Within normal function + // declarations there are no such restrictions. + if (emitterMode == EmitterMode::SelfHosting) { + if (sc->isTopLevelContext()) { + MOZ_ASSERT(!funbox->isLambda()); + MOZ_ASSERT(funbox->explicitName()); + prevSelfHostedTopLevelFunction = funbox; + } + } + + return true; +} + +bool BytecodeEmitter::emitDo(BinaryNode* doNode) { + ParseNode* bodyNode = doNode->left(); + + DoWhileEmitter doWhile(this); + if (!doWhile.emitBody(doNode->pn_pos.begin, getOffsetForLoop(bodyNode))) { + return false; + } + + if (!emitTree(bodyNode)) { + return false; + } + + if (!doWhile.emitCond()) { + return false; + } + + ParseNode* condNode = doNode->right(); + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!doWhile.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) { + ParseNode* bodyNode = whileNode->right(); + + WhileEmitter wh(this); + + ParseNode* condNode = whileNode->left(); + if (!wh.emitCond(whileNode->pn_pos.begin, getOffsetForLoop(condNode), + whileNode->pn_pos.end)) { + return false; + } + + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!wh.emitBody()) { + return false; + } + if (!emitTree(bodyNode)) { + return false; + } + + if (!wh.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitBreak(TaggedParserAtomIndex label) { + BreakableControl* target; + 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); + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is(); + }; + target = findInnermostNestableControl(isNotLabel); + } + + return emitGoto(target, GotoKind::Break); +} + +bool BytecodeEmitter::emitContinue(TaggedParserAtomIndex 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, GotoKind::Continue); +} + +bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { + MOZ_ASSERT(sc->hasFunctionThisBinding()); + MOZ_ASSERT(thisName->isName(TaggedParserAtomIndex::WellKnown::dot_this_())); + + if (!updateLineNumberNotes(thisName->pn_pos.begin)) { + return false; + } + + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_this_())) { + // [stack] THIS + return false; + } + if (sc->needsThisTDZChecks()) { + if (!emit1(JSOp::CheckThis)) { + // [stack] THIS + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) { + MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase)); + NameNode* nameNode = &superBase->kid()->as(); + return emitGetFunctionThis(nameNode); + // [stack] THIS +} + +bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { + if (ParseNode* kid = pn->kid()) { + NameNode* thisName = &kid->as(); + return emitGetFunctionThis(thisName); + // [stack] THIS + } + + if (sc->thisBinding() == ThisBinding::Module) { + return emit1(JSOp::Undefined); + // [stack] UNDEF + } + + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); + + MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() == + sc->hasNonSyntacticScope()); + if (sc->hasNonSyntacticScope()) { + return emit1(JSOp::NonSyntacticGlobalThis); + // [stack] THIS + } + + return emit1(JSOp::GlobalThis); + // [stack] THIS +} + +bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { + MOZ_ASSERT( + lookupName(TaggedParserAtomIndex::WellKnown::dot_this_()).hasKnownSlot()); + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_this_())) { + return false; + } + if (!emit1(JSOp::CheckReturn)) { + return false; + } + if (!emit1(JSOp::SetRval)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitNewTarget() { + MOZ_ASSERT(sc->allowNewTarget()); + + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_newTarget_())) { + // [stack] NEW.TARGET + return false; + } + return true; +} + +bool BytecodeEmitter::emitNewTarget(NewTargetNode* pn) { + MOZ_ASSERT(pn->newTargetName()->isName( + TaggedParserAtomIndex::WellKnown::dot_newTarget_())); + + return emitNewTarget(); +} + +bool BytecodeEmitter::emitNewTarget(CallNode* pn) { + MOZ_ASSERT(pn->callOp() == JSOp::SuperCall || + pn->callOp() == JSOp::SpreadSuperCall); + + // The parser is responsible for marking the "new.target" binding as being + // implicitly used in super() calls. + return emitNewTarget(); +} + +bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { + if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + + /* Push a return value */ + if (ParseNode* expr = returnNode->kid()) { + if (!emitTree(expr)) { + return false; + } + + if (sc->asSuspendableContext()->isAsync() && + sc->asSuspendableContext()->isGenerator()) { + if (!emitAwaitInInnermostScope()) { + return false; + } + } + } else { + /* No explicit return value provided */ + if (!emit1(JSOp::Undefined)) { + return false; + } + } + + // We know functionBodyEndPos is set because "return" is only + // valid in a function, and so we've passed through + // emitFunctionScript. + if (!updateSourceCoordNotes(*functionBodyEndPos)) { + return false; + } + + /* + * The return value is currently on the stack. We would like to + * generate JSOp::Return, but if we have work to do before returning, + * we will instead generate JSOp::SetRval / JSOp::RetRval. + * + * We don't know whether we will need fixup code until after calling + * prepareForNonLocalJumpToOutermost, so we start by generating + * JSOp::SetRval, then mutate it to JSOp::Return in finishReturn if it + * wasn't needed. + */ + BytecodeOffset setRvalOffset = bytecodeSection().offset(); + if (!emit1(JSOp::SetRval)) { + return false; + } + + NonLocalExitControl nle(this, NonLocalExitKind::Return); + return nle.emitReturn(setRvalOffset); +} + +bool BytecodeEmitter::finishReturn(BytecodeOffset setRvalOffset) { + // The return value is currently in rval. Depending on the current function, + // we may have to do additional work before returning: + // - Derived class constructors must check if the return value is an object. + // - Generators and async functions must do a final yield. + // - Non-async generators must return the value as an iterator result: + // { value: , done: true } + // - Non-generator async functions must resolve the function's result promise + // with the value. + // + // If we have not generated any code since the SetRval that stored the return + // value, we can also optimize the bytecode by rewriting that SetRval as a + // JSOp::Return. See |emitReturn| above. + + bool isDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + bool needsFinalYield = + sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); + bool isSimpleReturn = + setRvalOffset.valid() && + setRvalOffset + BytecodeOffsetDiff(JSOpLength_SetRval) == + bytecodeSection().offset(); + + if (isDerivedClassConstructor) { + MOZ_ASSERT(!needsFinalYield); + if (!emitJump(JSOp::Goto, &endOfDerivedClassConstructorBody)) { + return false; + } + return true; + } + + if (needsFinalYield) { + if (!emitJump(JSOp::Goto, &finalYields)) { + return false; + } + return true; + } + + if (isSimpleReturn) { + MOZ_ASSERT(JSOp(bytecodeSection().code()[setRvalOffset.value()]) == + JSOp::SetRval); + bytecodeSection().code()[setRvalOffset.value()] = jsbytecode(JSOp::Return); + return true; + } + + // Nothing special needs to be done. + return emitReturnRval(); +} + +bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { + if (!sc->isFunction() && sc->isModuleContext() && + sc->asModuleContext()->isAsync()) { + NameLocation loc = *locationOfNameBoundInScopeType( + TaggedParserAtomIndex::WellKnown::dot_generator_(), ¤tScope); + return emitGetNameAtLocation( + TaggedParserAtomIndex::WellKnown::dot_generator_(), loc); + } + NameLocation loc = *locationOfNameBoundInScopeType( + TaggedParserAtomIndex::WellKnown::dot_generator_(), ¤tScope); + return emitGetNameAtLocation( + TaggedParserAtomIndex::WellKnown::dot_generator_(), loc); +} + +bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) { + if (!emitTree(yieldNode->kid())) { + return false; + } + + if (!emitYieldOp(JSOp::InitialYield)) { + // [stack] RVAL GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RVAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) { + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(sc->asFunctionBox()->isGenerator()); + MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::YieldExpr)); + + bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] ITEROBJ + return false; + } + } + if (ParseNode* expr = yieldNode->kid()) { + if (!emitTree(expr)) { + // [stack] ITEROBJ? VAL + return false; + } + } else { + if (!emit1(JSOp::Undefined)) { + // [stack] ITEROBJ? UNDEFINED + return false; + } + } + + if (sc->asSuspendableContext()->isAsync()) { + MOZ_ASSERT(!needsIteratorResult); + if (!emitAwaitInInnermostScope()) { + // [stack] RESULT + return false; + } + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(false)) { + // [stack] ITEROBJ + return false; + } + } + + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] # if needsIteratorResult + // [stack] ITEROBJ .GENERATOR + // [stack] # else + // [stack] RESULT .GENERATOR + return false; + } + + if (!emitYieldOp(JSOp::Yield)) { + // [stack] YIELDRESULT GENERATOR RESUMEKIND + return false; + } + + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] YIELDRESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) { + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::AwaitExpr)); + + if (!emitTree(awaitNode->kid())) { + return false; + } + return emitAwaitInInnermostScope(); +} + +bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) { + if (!emit1(JSOp::CanSkipAwait)) { + // [stack] VALUE CANSKIP + return false; + } + + if (!emit1(JSOp::MaybeExtractAwaitValue)) { + // [stack] VALUE_OR_RESOLVED CANSKIP + return false; + } + + InternalIfEmitter ifCanSkip(this); + if (!ifCanSkip.emitThen(IfEmitter::ConditionKind::Negative)) { + // [stack] VALUE_OR_RESOLVED + return false; + } + + if (sc->asSuspendableContext()->needsPromiseResult()) { + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE GENERATOR + return false; + } + if (!emit1(JSOp::AsyncAwait)) { + // [stack] PROMISE + return false; + } + } + + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE|PROMISE GENERATOR + return false; + } + if (!emitYieldOp(JSOp::Await)) { + // [stack] RESOLVED GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RESOLVED + return false; + } + + if (!ifCanSkip.emitEnd()) { + return false; + } + + MOZ_ASSERT(ifCanSkip.popped() == 0); + + return true; +} + +// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c +// 14.4.14 Runtime Semantics: Evaluation +// YieldExpression : yield* AssignmentExpression +bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { + MOZ_ASSERT(getSelfHostedIterFor(iter) == SelfHostedIter::Deny, + "yield* is prohibited in self-hosted code because it can run " + "user-modifiable iteration code"); + + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(sc->asSuspendableContext()->isGenerator()); + + // Step 1. + IteratorKind iterKind = sc->asSuspendableContext()->isAsync() + ? IteratorKind::Async + : IteratorKind::Sync; + bool needsIteratorResult = sc->asSuspendableContext()->needsIteratorResult(); + + // Steps 2-5. + if (!emitTree(iter)) { + // [stack] ITERABLE + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAsyncIterator(SelfHostedIter::Deny)) { + // [stack] NEXT ITER + return false; + } + } else { + if (!emitIterator(SelfHostedIter::Deny)) { + // [stack] NEXT ITER + return false; + } + } + + // Step 6. + // Start with NormalCompletion(undefined). + if (!emit1(JSOp::Undefined)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + const int32_t startDepth = bytecodeSection().stackDepth(); + MOZ_ASSERT(startDepth >= 4); + + // Step 7 is a loop. + LoopControl loopInfo(this, StatementKind::YieldStar); + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + // Step 7.a. Check for Normal completion. + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND NORMAL + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_NORMAL + return false; + } + + InternalIfEmitter ifKind(this); + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.a.i. + // result = iter.next(received) + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RECEIVED NEXT ITER + return false; + } + if (!emit1(JSOp::Dup2)) { + // [stack] RECEIVED NEXT ITER NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 4)) { + // [stack] NEXT ITER NEXT ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.a.ii. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.a.iii. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.a.iv-vii is emitted after the ifKind if-else because + // it's shared with other branches. + } + + // Step 7.b. Check for Throw completion. + if (!ifKind.emitElseIf(Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Throw)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND THROW + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_THROW + return false; + } + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + // Step 7.b.i. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::throw_())) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii. + InternalIfEmitter ifThrowMethodIsNotDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] NEXT ITER RECEIVED ITER THROW NULL-OR-UNDEF + return false; + } + + if (!ifThrowMethodIsNotDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii.1. + // RESULT = ITER.throw(EXCEPTION) + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED THROW ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER THROW ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.b.ii.2. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.b.ii.4. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.b.ii.5-8 is emitted after the ifKind if-else because + // it's shared with other branches. + + // Step 7.b.iii. + if (!ifThrowMethodIsNotDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + + // Steps 7.b.iii.1-4. + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorCloseInInnermostScope(iterKind, CompletionKind::Normal)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + // Steps 7.b.iii.5-6. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::IteratorNoThrow))) { + // [stack] NEXT ITER RECEIVED ITER + // [stack] # throw + return false; + } + + if (!ifThrowMethodIsNotDefined.emitEnd()) { + return false; + } + } + + // Step 7.c. It must be a Return completion. + if (!ifKind.emitElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.c.i. + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + // Step 7.c.ii. + // + // Get the "return" method. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::return_())) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + + // Step 7.c.iii. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] NEXT ITER RECEIVED ITER RET NULL-OR-UNDEF + return false; + } + + // Step 7.c.iv. + // + // Call "return" with the argument passed to Generator.prototype.return. + if (!ifReturnMethodIsDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED RET ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER RET ITER RECEIVED + return false; + } + if (needsIteratorResult) { + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER RET ITER VAL + return false; + } + } + if (!emitCall(JSOp::Call, 1)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.v. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.c.vi. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Check if the returned object from iterator.return() is done. If not, + // continue yielding. + + // Steps 7.c.vii-viii. + InternalIfEmitter ifReturnDone(this); + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!ifReturnDone.emitThenElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.viii.1. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER VALUE + return false; + } + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] NEXT ITER VALUE RESULT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RESULT VALUE + return false; + } + if (!emitFinishIteratorResult(true)) { + // [stack] NEXT ITER RESULT + return false; + } + } + + if (!ifReturnDone.emitElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Jump to continue label for steps 7.c.ix-x. + if (!emitJump(JSOp::Goto, &loopInfo.continues)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!ifReturnDone.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.iii. + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emitPopN(2)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (iterKind == IteratorKind::Async) { + // Step 7.c.iii.1. + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RECEIVED + return false; + } + } + if (!ifReturnMethodIsDefined.emitEnd()) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Perform a "forced generator return". + // + // Step 7.c.iii.2. + // Step 7.c.viii.2. + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Return)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + } + + if (!ifKind.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Shared tail for Normal/Throw completions. + // + // Steps 7.a.iv-v. + // Steps 7.b.ii.5-6. + // + // [stack] NEXT ITER RESULT + + // if (result.done) break; + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Steps 7.a.vi-vii. + // Steps 7.b.ii.7-8. + // Steps 7.c.ix-x. + if (!loopInfo.emitContinueTarget(this)) { + // [stack] NEXT ITER RESULT + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER RESULT + return false; + } + } + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitYieldOp(JSOp::Yield)) { + // [stack] NEXT ITER RVAL GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RVAL RESUMEKIND GENOBJ + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::Loop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + + // Jumps to this point have 3 (instead of 4) values on the stack. + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth); + bytecodeSection().setStackDepth(startDepth - 1); + + // [stack] NEXT ITER RESULT + + // Step 7.a.v.1. + // Step 7.b.ii.6.a. + // + // result.value + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RESULT NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] VALUE + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth - 3); + + 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::ExpressionStmt)); + + /* + * 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 ReadOnlyCompileOptions::noScriptRval option when + * calling JS_Compile* to suppress JSOp::SetRval. + */ + bool wantval = false; + bool useful = false; + if (sc->isTopLevelContext()) { + useful = wantval = !sc->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() >= + bytecodeSection().offset()) { + useful = true; + } + } + + if (useful) { + ValueUsage valueUsage = + wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; + ExpressionStatementEmitter ese(this, valueUsage); + if (!ese.prepareForExpr(exprStmt->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr, valueUsage)) { + return false; + } + if (!ese.emitEnd()) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteNameExpr)); + + NameNode* nameExpr = &deleteNode->kid()->as(); + MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); + + return emitAtomOp(JSOp::DelName, nameExpr->atom()); +} + +bool BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeletePropExpr)); + + PropertyAccess* propExpr = &deleteNode->kid()->as(); + 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)) { + // [stack] THIS + return false; + } + } else { + if (!poe.prepareForObj()) { + return false; + } + if (!emitPropLHS(propExpr)) { + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().atom())) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElemExpr)); + + PropertyByValue* elemExpr = &deleteNode->kid()->as(); + bool isSuper = elemExpr->isSuper(); + DebugOnly isPrivate = + elemExpr->key().isKind(ParseNodeKind::PrivateName); + MOZ_ASSERT(!isPrivate); + 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()) { + // [stack] + return false; + } + + UnaryNode* base = &elemExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(&elemExpr->key())) { + // [stack] THIS KEY + return false; + } + } else { + if (!emitElemObjAndKey(elemExpr, false, eoe)) { + // [stack] OBJ KEY + return false; + } + } + if (!eoe.emitDelete()) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] 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); +} + +bool BytecodeEmitter::emitDeleteOptionalChain(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteOptionalChainExpr)); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + ParseNode* kid = deleteNode->kid(); + switch (kid->getKind()) { + case ParseNodeKind::ElemExpr: + case ParseNodeKind::OptionalElemExpr: { + auto* elemExpr = &kid->as(); + if (!emitDeleteElementInOptChain(elemExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + break; + } + case ParseNodeKind::DotExpr: + case ParseNodeKind::OptionalDotExpr: { + auto* propExpr = &kid->as(); + if (!emitDeletePropertyInOptChain(propExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind"); + } + + if (!oe.emitOptionalJumpTarget(JSOp::True)) { + // [stack] # If shortcircuit + // [stack] TRUE + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe) { + MOZ_ASSERT_IF(propExpr->is(), + !propExpr->as().isSuper()); + PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, + PropOpEmitter::ObjKind::Other); + + if (!poe.prepareForObj()) { + // [stack] + return false; + } + if (!emitOptionalTree(&propExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + if (propExpr->isKind(ParseNodeKind::OptionalDotExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().atom())) { + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe) { + MOZ_ASSERT_IF(elemExpr->is(), + !elemExpr->as().isSuper()); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, + ElemOpEmitter::ObjKind::Other); + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (!emitOptionalTree(&elemExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + + if (elemExpr->isKind(ParseNodeKind::OptionalElemExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ + return false; + } + + if (!emitTree(&elemExpr->key())) { + // [stack] OBJ KEY + return false; + } + + if (!eoe.emitDelete()) { + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDebugCheckSelfHosted() { + // [stack] CALLEE + +#ifdef DEBUG + if (!emit1(JSOp::DebugCheckSelfHosted)) { + // [stack] CALLEE + return false; + } +#endif + + return true; +} + +bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode, JSOp op) { + // 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->callee()->as(); + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() >= 2); + + MOZ_ASSERT(callNode->callOp() == JSOp::Call); + + bool constructing = + calleeNode->name() == + TaggedParserAtomIndex::WellKnown::constructContentFunction(); + ParseNode* funNode = argsList->head(); + + if (!emitTree(funNode)) { + // [stack] CALLEE + return false; + } + +#ifdef DEBUG + MOZ_ASSERT(op == JSOp::Call || op == JSOp::CallContent || + op == JSOp::NewContent); + if (op == JSOp::Call) { + if (!emitDebugCheckSelfHosted()) { + // [stack] CALLEE + 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::IsConstructing)) { + // [stack] CALLEE IS_CONSTRUCTING + return false; + } + } else { + // It's |this|, emit it. + if (!emitTree(thisOrNewTarget)) { + // [stack] CALLEE THIS + return false; + } + } + + for (ParseNode* argpn : argsList->contentsFrom(thisOrNewTarget->pn_next)) { + if (!emitTree(argpn)) { + // [stack] CALLEE ... ARGS... + return false; + } + } + + if (constructing) { + if (!emitTree(thisOrNewTarget)) { + // [stack] CALLEE IS_CONSTRUCTING ARGS... NEW.TARGET + return false; + } + } + + uint32_t argc = argsList->count() - 2; + if (!emitCall(op, argc)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedResumeGenerator(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* genNode = argsList->head(); + if (!emitTree(genNode)) { + // [stack] GENERATOR + return false; + } + + ParseNode* valNode = genNode->pn_next; + if (!emitTree(valNode)) { + // [stack] GENERATOR VALUE + return false; + } + + ParseNode* kindNode = valNode->pn_next; + MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr)); + GeneratorResumeKind kind = + ParserAtomToResumeKind(kindNode->as().atom()); + MOZ_ASSERT(!kindNode->pn_next); + + if (!emitPushResumeKind(kind)) { + // [stack] GENERATOR VALUE RESUMEKIND + return false; + } + + if (!emit1(JSOp::Resume)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedForceInterpreter() { + // JSScript::hasForceInterpreterOp() relies on JSOp::ForceInterpreter being + // the first bytecode op in the script. + MOZ_ASSERT(bytecodeSection().code().empty()); + + if (!emit1(JSOp::ForceInterpreter)) { + return false; + } + if (!emit1(JSOp::Undefined)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIter(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIterWith(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 2 || argsList->count() == 3); + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIterWithNext( + CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 2); + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedDefineDataProperty(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + // 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(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 2); + + 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(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 3); + + 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::GetElemSuper); +} + +bool BytecodeEmitter::emitSelfHostedToNumeric(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToNumeric); +} + +bool BytecodeEmitter::emitSelfHostedToString(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToString); +} + +bool BytecodeEmitter::emitSelfHostedIsNullOrUndefined(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + // [stack] ARG + return false; + } + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] ARG IS_NULL_OR_UNDEF + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] IS_NULL_OR_UNDEF ARG + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] IS_NULL_OR_UNDEF + return false; + } + return true; +} + +bool BytecodeEmitter::emitSelfHostedIteratorClose(CallNode* callNode) { + ListNode* argsList = callNode->args(); + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + if (!emitTree(argNode)) { + // [stack] ARG + return false; + } + + if (!emit2(JSOp::CloseIter, uint8_t(CompletionKind::Normal))) { + // [stack] + return false; + } + + // This is still a call node, so we must generate a stack value. + if (!emit1(JSOp::Undefined)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype( + CallNode* callNode, bool isConstructor) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + auto name = argNode->as().atom(); + + BuiltinObjectKind kind; + if (isConstructor) { + kind = BuiltinConstructorForName(name); + } else { + kind = BuiltinPrototypeForName(name); + } + + if (kind == BuiltinObjectKind::None) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a valid built-in"); + return false; + } + + return emitBuiltinObject(kind); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor(CallNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ true); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(CallNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ false); +} + +JS::SymbolCode ParserAtomToSymbolCode(TaggedParserAtomIndex atom) { + // NOTE: This is a linear search, but the set of entries is quite small and + // this is only used for initial self-hosted parse. +#define MATCH_WELL_KNOWN_SYMBOL(NAME) \ + if (atom == TaggedParserAtomIndex::WellKnown::NAME()) { \ + return JS::SymbolCode::NAME; \ + } + JS_FOR_EACH_WELL_KNOWN_SYMBOL(MATCH_WELL_KNOWN_SYMBOL) +#undef MATCH_WELL_KNOWN_SYMBOL + + return JS::SymbolCode::Limit; +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinSymbol(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + auto name = argNode->as().atom(); + + JS::SymbolCode code = ParserAtomToSymbolCode(name); + if (code == JS::SymbolCode::Limit) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a valid built-in"); + return false; + } + + return emit2(JSOp::Symbol, uint8_t(code)); +} + +bool BytecodeEmitter::emitSelfHostedArgumentsLength(CallNode* callNode) { + MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj()); + sc->asFunctionBox()->setUsesArgumentsIntrinsics(); + + MOZ_ASSERT(callNode->args()->count() == 0); + + return emit1(JSOp::ArgumentsLength); +} + +bool BytecodeEmitter::emitSelfHostedGetArgument(CallNode* callNode) { + MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj()); + sc->asFunctionBox()->setUsesArgumentsIntrinsics(); + + ListNode* argsList = callNode->args(); + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::GetActualArg); +} + +#ifdef DEBUG +void BytecodeEmitter::assertSelfHostedExpectedTopLevel(ParseNode* node) { + // The function argument is expected to be a simple binding/function name. + // Eg. `function foo() { }; SpecialIntrinsic(foo)` + MOZ_ASSERT(node->isKind(ParseNodeKind::Name), + "argument must be a function name"); + TaggedParserAtomIndex targetName = node->as().name(); + + // The special intrinsics must follow the target functions definition. A + // simple assert is fine here since any hoisted function will cause a non-null + // value to be set here. + MOZ_ASSERT(prevSelfHostedTopLevelFunction); + + // The target function must match the most recently defined top-level + // self-hosted function. + MOZ_ASSERT(prevSelfHostedTopLevelFunction->explicitName() == targetName, + "selfhost decorator must immediately follow target function"); +} +#endif + +bool BytecodeEmitter::emitSelfHostedSetIsInlinableLargeFunction( + CallNode* callNode) { +#ifdef DEBUG + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 1); + + assertSelfHostedExpectedTopLevel(argsList->head()); +#endif + + MOZ_ASSERT(prevSelfHostedTopLevelFunction->isInitialCompilation); + prevSelfHostedTopLevelFunction->setIsInlinableLargeFunction(); + + // This is still a call node, so we must generate a stack value. + return emit1(JSOp::Undefined); +} + +bool BytecodeEmitter::emitSelfHostedSetCanonicalName(CallNode* callNode) { + ListNode* argsList = callNode->args(); + + MOZ_ASSERT(argsList->count() == 2); + +#ifdef DEBUG + assertSelfHostedExpectedTopLevel(argsList->head()); +#endif + + ParseNode* nameNode = argsList->last(); + MOZ_ASSERT(nameNode->isKind(ParseNodeKind::StringExpr)); + TaggedParserAtomIndex specName = nameNode->as().atom(); + // Canonical name must be atomized. + compilationState.parserAtoms.markUsedByStencil(specName, + ParserAtom::Atomize::Yes); + + // Store the canonical name for instantiation. + prevSelfHostedTopLevelFunction->functionStencil().setSelfHostedCanonicalName( + specName); + + return emit1(JSOp::Undefined); +} + +#ifdef DEBUG +void BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot( + ListNode* argsList) { + MOZ_ASSERT(argsList->count() == 2); + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr), + "slot argument must be a constant"); +} + +void BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot( + ListNode* argsList) { + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr), + "slot argument must be a constant"); +} +#endif + +/* A version of emitCalleeAndThis for the optional cases: + * * a?.() + * * a?.b() + * * a?.["b"]() + * * (a?.b)() + * * a?.#b() + * + * See emitCallOrNew and emitOptionalCall for more context. + */ +bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + + switch (ParseNodeKind kind = callee->getKind()) { + case ParseNodeKind::Name: { + auto name = callee->as().name(); + if (!cone.emitNameCallee(name)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalDotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyAccess* prop = &callee->as(); + bool isSuper = false; + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &callee->as(); + bool isSuper = false; + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: { + PrivateMemberAccessBase* privateExpr = + &callee->as(); + PrivateOpEmitter& xoe = + cone.prepareForPrivateCallee(privateExpr->privateName().name()); + if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; + } + break; + + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as(), call, + cone); + } + + default: + MOZ_RELEASE_ASSERT(kind != ParseNodeKind::SuperBase); + + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, CallNode* maybeCall, + CallOrNewEmitter& cone) { + MOZ_ASSERT_IF(maybeCall, maybeCall->callee() == callee); + + switch (callee->getKind()) { + case ParseNodeKind::Name: { + auto name = callee->as().name(); + if (!cone.emitNameCallee(name)) { + // [stack] CALLEE THIS? + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] CALLEE THIS? + return false; + } + + break; + } + case ParseNodeKind::ElemExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS? THIS KEY + // [stack] # otherwise + // [stack] OBJ? OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] CALLEE THIS? + return false; + } + + break; + } + case ParseNodeKind::PrivateMemberExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PrivateMemberAccessBase* privateExpr = + &callee->as(); + PrivateOpEmitter& xoe = + cone.prepareForPrivateCallee(privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGetForCallOrNew()) { + // [stack] CALLEE THIS + return false; + } + + break; + } + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitTree(callee)) { + // [stack] CALLEE + return false; + } + break; + case ParseNodeKind::SuperBase: + MOZ_ASSERT(maybeCall); + MOZ_ASSERT(maybeCall->isKind(ParseNodeKind::SuperCallExpr)); + MOZ_ASSERT(callee->isKind(ParseNodeKind::SuperBase)); + if (!cone.emitSuperCallee()) { + // [stack] CALLEE IsConstructing + return false; + } + break; + case ParseNodeKind::OptionalChain: { + MOZ_ASSERT(maybeCall); + return emitCalleeAndThisForOptionalChain(&callee->as(), + maybeCall, cone); + } + default: + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitTree(callee)) { + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList) { + ParseNode* coordNode = callNode; + if (op == JSOp::Call || op == JSOp::SpreadCall) { + // Default to using the location of the `(` itself. + // obj[expr]() // expression + // ^ // column coord + coordNode = argsList; + + switch (calleeNode->getKind()) { + case ParseNodeKind::DotExpr: + // Use the position of a property access identifier. + // + // 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::Name: { + // Use the start of callee name unless it is at a separator + // or has no args. + // + // 2 + obj() // expression + // ^ // column coord + // + if (argsList->empty() || + !bytecodeSection().atSeparator(calleeNode->pn_pos.begin)) { + // Use the start of callee names. + coordNode = calleeNode; + } + break; + } + + default: + break; + } + } + return coordNode; +} + +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()) { + // [stack] CALLEE THIS + return false; + } + for (ParseNode* arg : argsList->contents()) { + if (!updateSourceCoordNotesIfNonLiteral(arg)) { + return false; + } + if (!emitTree(arg)) { + // [stack] CALLEE THIS ARG* + return false; + } + } + } else if (cone.wantSpreadOperand()) { + auto* spreadNode = &argsList->head()->as(); + if (!updateSourceCoordNotesIfNonLiteral(spreadNode->kid())) { + return false; + } + if (!emitTree(spreadNode->kid())) { + // [stack] CALLEE THIS ARG0 + return false; + } + + if (!cone.emitSpreadArgumentsTest()) { + // [stack] CALLEE THIS ARG0 + return false; + } + + if (cone.wantSpreadIteration()) { + if (!emitSpreadIntoArray(spreadNode)) { + // [stack] CALLEE THIS ARR + return false; + } + } + + if (!cone.emitSpreadArgumentsTestEnd()) { + // [stack] CALLEE THIS ARR + return false; + } + } else { + if (!cone.prepareForSpreadArguments()) { + // [stack] CALLEE THIS + return false; + } + if (!emitArray(argsList)) { + // [stack] CALLEE THIS ARR + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage) { + /* + * A modified version of emitCallOrNew that handles optional calls. + * + * These include the following: + * a?.() + * a.b?.() + * a.["b"]?.() + * (a?.b)?.() + * + * See CallOrNewEmitter for more context. + */ + ParseNode* calleeNode = callNode->callee(); + ListNode* argsList = callNode->args(); + bool isSpread = IsSpreadOp(callNode->callOp()); + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + bool isOptimizableSpread = isSpread && argc == 1; + + CallOrNewEmitter cone(this, op, + isOptimizableSpread + ? CallOrNewEmitter::ArgumentsKind::SingleSpread + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + if (callNode->isKind(ParseNodeKind::OptionalCallExpr)) { + if (!oe.emitJumpShortCircuitForCall()) { + // [stack] CALLEE THIS + return false; + } + } + + if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallOrNew(CallNode* callNode, ValueUsage valueUsage) { + /* + * Emit callable invocation or operator new (constructor call) code. + * First, emit code for the left operand to evaluate the callable or + * constructable object expression. + * + * 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::CallExpr) || + callNode->isKind(ParseNodeKind::TaggedTemplateExpr); + ParseNode* calleeNode = callNode->callee(); + ListNode* argsList = callNode->args(); + JSOp op = callNode->callOp(); + + if (calleeNode->isKind(ParseNodeKind::Name) && + emitterMode == BytecodeEmitter::SelfHosting && op == JSOp::Call) { + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. + // + // NOTE: The list of special instruction names has to be kept in sync with + // "js/src/builtin/.eslintrc.js". + auto calleeName = calleeNode->as().name(); + if (calleeName == TaggedParserAtomIndex::WellKnown::callFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::Call); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::callContentFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::CallContent); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::constructContentFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::NewContent); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::resumeGenerator()) { + return emitSelfHostedResumeGenerator(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::forceInterpreter()) { + return emitSelfHostedForceInterpreter(); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::allowContentIter()) { + return emitSelfHostedAllowContentIter(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::allowContentIterWith()) { + return emitSelfHostedAllowContentIterWith(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::allowContentIterWithNext()) { + return emitSelfHostedAllowContentIterWithNext(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::DefineDataProperty() && + argsList->count() == 3) { + return emitSelfHostedDefineDataProperty(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::hasOwn()) { + return emitSelfHostedHasOwn(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::getPropertySuper()) { + return emitSelfHostedGetPropertySuper(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ToNumeric()) { + return emitSelfHostedToNumeric(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ToString()) { + return emitSelfHostedToString(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::GetBuiltinConstructor()) { + return emitSelfHostedGetBuiltinConstructor(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinPrototype()) { + return emitSelfHostedGetBuiltinPrototype(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinSymbol()) { + return emitSelfHostedGetBuiltinSymbol(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ArgumentsLength()) { + return emitSelfHostedArgumentsLength(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetArgument()) { + return emitSelfHostedGetArgument(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::SetIsInlinableLargeFunction()) { + return emitSelfHostedSetIsInlinableLargeFunction(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::SetCanonicalName()) { + return emitSelfHostedSetCanonicalName(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::IsNullOrUndefined()) { + return emitSelfHostedIsNullOrUndefined(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::IteratorClose()) { + return emitSelfHostedIteratorClose(callNode); + } +#ifdef DEBUG + if (calleeName == + TaggedParserAtomIndex::WellKnown::UnsafeGetReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetObjectFromReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetInt32FromReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetStringFromReservedSlot()) { + // Make sure that this call is correct, but don't emit any special code. + assertSelfHostedUnsafeGetReservedSlot(argsList); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::UnsafeSetReservedSlot()) { + // Make sure that this call is correct, but don't emit any special code. + assertSelfHostedUnsafeSetReservedSlot(argsList); + } +#endif + // Fall through + } + + uint32_t argc = argsList->count(); + bool isSpread = IsSpreadOp(op); + bool isOptimizableSpread = isSpread && argc == 1; + bool isDefaultDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor() && + sc->asFunctionBox()->isSyntheticFunction(); + MOZ_ASSERT_IF(isDefaultDerivedClassConstructor, isOptimizableSpread); + CallOrNewEmitter cone( + this, op, + isOptimizableSpread + ? isDefaultDerivedClassConstructor + ? CallOrNewEmitter::ArgumentsKind::PassthroughRest + : CallOrNewEmitter::ArgumentsKind::SingleSpread + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + if (!emitCalleeAndThis(calleeNode, callNode, cone)) { + // [stack] CALLEE THIS + return false; + } + if (!emitArguments(argsList, isCall, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + // Push new.target for construct calls. + if (IsConstructOp(op)) { + if (op == JSOp::SuperCall || op == JSOp::SpreadSuperCall) { + if (!emitNewTarget(callNode)) { + // [stack] CALLEE THIS ARGS.. NEW.TARGET + return false; + } + } else { + // Repush the callee as new.target + uint32_t effectiveArgc = isSpread ? 1 : argc; + if (!emitDupAt(effectiveArgc + 1)) { + // [stack] CALLEE THIS ARGS.. CALLEE + return false; + } + } + } + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) { + // [stack] RVAL + return false; + } + + return true; +} + +// This list must be kept in the same order in several places: +// - The binary operators in ParseNode.h , +// - the binary operators in TokenKind.h +// - the precedence list in Parser.cpp +static const JSOp ParseNodeKindToJSOp[] = { + // Some binary ops require special code generation (PrivateIn); + // these should not use BinaryOpParseNodeKindToJSOp. This table fills those + // slots with Nops to make the rest of the table lookup work. + JSOp::Coalesce, 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::Nop, 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); + int parseNodeFirst = size_t(ParseNodeKind::BinOpFirst); +#ifdef DEBUG + int jsopArraySize = std::size(ParseNodeKindToJSOp); + int parseNodeKindListSize = + size_t(ParseNodeKind::BinOpLast) - parseNodeFirst + 1; + MOZ_ASSERT(jsopArraySize == parseNodeKindListSize); + // Ensure we don't use this to find an op for a parse node + // requiring special emission rules. + MOZ_ASSERT(ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst] != JSOp::Nop); +#endif + return ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst]; +} + +bool BytecodeEmitter::emitRightAssociative(ListNode* node) { + // ** is the only right-associative operator. + MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); + + // Right-associative operator chain. + for (ParseNode* subexpr : node->contents()) { + if (!updateSourceCoordNotesIfNonLiteral(subexpr)) { + return false; + } + 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 (!updateSourceCoordNotesIfNonLiteral(nextExpr)) { + return false; + } + if (!emitTree(nextExpr)) { + return false; + } + if (!emit1(op)) { + return false; + } + } while ((nextExpr = nextExpr->pn_next)); + return true; +} + +bool BytecodeEmitter::emitPrivateInExpr(ListNode* node) { + MOZ_ASSERT(node->head()->isKind(ParseNodeKind::PrivateName)); + + NameNode& privateNameNode = node->head()->as(); + TaggedParserAtomIndex privateName = privateNameNode.name(); + + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::ErgonomicBrandCheck, + privateName); + + ParseNode* valueNode = node->head()->pn_next; + MOZ_ASSERT(valueNode->pn_next == nullptr); + + if (!emitTree(valueNode)) { + // [stack] OBJ + return false; + } + + if (!xoe.emitReference()) { + // [stack] OBJ BRAND if private method + // [stack] OBJ NAME if private field or accessor. + return false; + } + + if (!xoe.emitBrandCheck()) { + // [stack] OBJ BRAND BOOL if private method + // [stack] OBJ NAME BOOL if private field or accessor. + return false; + } + + if (!emitUnpickN(2)) { + // [stack] BOOL OBJ BRAND if private method + // [stack] BOOL OBJ NAME if private field or accessor. + return false; + } + + if (!emitPopN(2)) { + // [stack] BOOL + return false; + } + + return true; +} + +/* + * Special `emitTree` for Optional Chaining case. + * Examples of this are `emitOptionalChain`, `emitDeleteOptionalChain` and + * `emitCalleeAndThisForOptionalChain`. + */ +bool BytecodeEmitter::emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + ParseNodeKind kind = pn->getKind(); + switch (kind) { + case ParseNodeKind::OptionalDotExpr: { + OptionalPropertyAccess* prop = &pn->as(); + bool isSuper = false; + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &pn->as(); + bool isSuper = false; + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + ElemOpEmitter::ObjKind::Other); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: { + PrivateMemberAccessBase* privateExpr = &pn->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get, + privateExpr->privateName().name()); + if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) { + return false; + } + break; + } + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + if (!emitOptionalCall(&pn->as(), oe, valueUsage)) { + return false; + } + break; + // List of accepted ParseNodeKinds that might appear only at the beginning + // of an Optional Chain. + // For example, a taggedTemplateExpr node might occur if we have + // `test`?.b, with `test` as the taggedTemplateExpr ParseNode. + default: +#ifdef DEBUG + // https://tc39.es/ecma262/#sec-primary-expression + bool isPrimaryExpression = + kind == ParseNodeKind::ThisExpr || kind == ParseNodeKind::Name || + kind == ParseNodeKind::PrivateName || + kind == ParseNodeKind::NullExpr || kind == ParseNodeKind::TrueExpr || + kind == ParseNodeKind::FalseExpr || + kind == ParseNodeKind::NumberExpr || + kind == ParseNodeKind::BigIntExpr || + kind == ParseNodeKind::StringExpr || + kind == ParseNodeKind::ArrayExpr || + kind == ParseNodeKind::ObjectExpr || + kind == ParseNodeKind::Function || kind == ParseNodeKind::ClassDecl || + kind == ParseNodeKind::RegExpExpr || + kind == ParseNodeKind::TemplateStringExpr || + kind == ParseNodeKind::TemplateStringListExpr || + kind == ParseNodeKind::RawUndefinedExpr || pn->isInParens(); + + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + bool isMemberExpression = isPrimaryExpression || + kind == ParseNodeKind::TaggedTemplateExpr || + kind == ParseNodeKind::NewExpr || + kind == ParseNodeKind::NewTargetExpr || + kind == ParseNodeKind::ImportMetaExpr; + + bool isCallExpression = kind == ParseNodeKind::SetThis || + kind == ParseNodeKind::CallImportExpr; + + MOZ_ASSERT(isMemberExpression || isCallExpression, + "Unknown ParseNodeKind for OptionalChain"); +#endif + return emitTree(pn); + } + return true; +} + +// Handle the case of a call made on a OptionalChainParseNode. +// For example `(a?.b)()` and `(a?.b)?.()`. +bool BytecodeEmitter::emitCalleeAndThisForOptionalChain( + UnaryNode* optionalChain, CallNode* callNode, CallOrNewEmitter& cone) { + ParseNode* calleeNode = optionalChain->kid(); + + // Create a new OptionalEmitter, in order to emit the right bytecode + // in isolation. + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + // complete the jump if necessary. This will set both the "this" value + // and the "callee" value to undefined, if the callee is undefined. It + // does not matter much what the this value is, the function call will + // fail if it is not optional, and be set to undefined otherwise. + if (!oe.emitOptionalJumpTarget(JSOp::Undefined, + OptionalEmitter::Kind::Reference)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED UNDEFINED + // [stack] # otherwise + // [stack] CALLEE THIS + return false; + } + return true; +} + +bool BytecodeEmitter::emitOptionalChain(UnaryNode* optionalChain, + ValueUsage valueUsage) { + ParseNode* expr = optionalChain->kid(); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalTree(expr, oe, valueUsage)) { + // [stack] VAL + return false; + } + + if (!oe.emitOptionalJumpTarget(JSOp::Undefined)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED + // [stack] # otherwise + // [stack] VAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalDotExpression(PropertyAccessBase* prop, + PropOpEmitter& poe, + bool isSuper, + OptionalEmitter& oe) { + if (!poe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&prop->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (prop->isKind(ParseNodeKind::OptionalDotExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& eoe, + bool isSuper, + OptionalEmitter& oe) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &elem->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&elem->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (elem->isKind(ParseNodeKind::OptionalElemExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY + return false; + } + + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalPrivateExpression( + PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe, + OptionalEmitter& oe) { + if (!emitOptionalTree(&privateExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + + if (privateExpr->isKind(ParseNodeKind::OptionalPrivateMemberExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGet()) { + // [stack] CALLEE THIS # if call + // [stack] VALUE # otherwise + return false; + } + + return true; +} + +bool BytecodeEmitter::emitShortCircuit(ListNode* node, ValueUsage valueUsage) { + MOZ_ASSERT(node->isKind(ParseNodeKind::OrExpr) || + node->isKind(ParseNodeKind::CoalesceExpr) || + node->isKind(ParseNodeKind::AndExpr)); + + /* + * 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); + + JSOp op; + switch (node->getKind()) { + case ParseNodeKind::OrExpr: + op = JSOp::Or; + break; + case ParseNodeKind::CoalesceExpr: + op = JSOp::Coalesce; + break; + case ParseNodeKind::AndExpr: + op = JSOp::And; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + JumpList jump; + + // Left-associative operator chain: avoid too much recursion. + // + // Emit all nodes but the last. + for (ParseNode* expr : node->contentsTo(node->last())) { + if (!emitTree(expr)) { + return false; + } + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + // Emit the last node + if (!emitTree(node->last(), valueUsage)) { + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitSequenceExpr(ListNode* node, ValueUsage valueUsage) { + for (ParseNode* child : node->contentsTo(node->last())) { + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, ValueUsage::IgnoreValue)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + ParseNode* child = node->last(); + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, valueUsage)) { + 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, + ValueUsage valueUsage) { + switch (incDec->kid()->getKind()) { + case ParseNodeKind::DotExpr: + return emitPropIncDec(incDec, valueUsage); + case ParseNodeKind::ElemExpr: + return emitElemIncDec(incDec, valueUsage); + case ParseNodeKind::PrivateMemberExpr: + return emitPrivateIncDec(incDec, valueUsage); + case ParseNodeKind::CallExpr: + return emitCallIncDec(incDec); + default: + return emitNameIncDec(incDec, valueUsage); + } +} + +// 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* labeledStmt) { + auto name = labeledStmt->label(); + LabelEmitter label(this); + + label.emitLabel(name); + + if (!emitTree(labeledStmt->statement())) { + return false; + } + if (!label.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitConditionalExpression( + ConditionalExpression& conditional, ValueUsage valueUsage) { + CondEmitter cond(this); + if (!cond.emitCond()) { + return false; + } + + ParseNode* conditionNode = &conditional.condition(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (conditionNode->isKind(ParseNodeKind::NotExpr)) { + conditionNode = conditionNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + // NOTE: NotExpr of conditionNode may be unwrapped, and in that case the + // negation is handled by conditionKind. + if (!emitTree(conditionNode)) { + return false; + } + + if (!cond.emitThenElse(conditionKind)) { + 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; +} + +// Check for an object-literal property list that can be handled by the +// ObjLiteral writer. We ensure that for each `prop: value` pair, the key is a +// constant name or numeric index, there is no accessor specified, and the value +// can be encoded by an ObjLiteral instruction (constant number, string, +// boolean, null/undefined). +void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj, + bool* withValues, + bool* withoutValues) { + bool keysOK = true; + bool valuesOK = true; + uint32_t propCount = 0; + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + keysOK = false; + break; + } + propCount++; + + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* value = prop->right(); + + // Computed keys not OK (ObjLiteral data stores constant keys). + if (key->isKind(ParseNodeKind::ComputedName)) { + keysOK = false; + break; + } + + // BigIntExprs should have been lowered to computed names at parse + // time, and so should be excluded above. + MOZ_ASSERT(!key->isKind(ParseNodeKind::BigIntExpr)); + + // Numeric keys OK as long as they are integers and in range. + if (key->isKind(ParseNodeKind::NumberExpr)) { + double numValue = key->as().value(); + int32_t i = 0; + if (!NumberIsInt32(numValue, &i)) { + keysOK = false; + break; + } + if (!ObjLiteralWriter::arrayIndexInRange(i)) { + keysOK = false; + break; + } + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr)); + + AccessorType accessorType = + prop->is() + ? prop->as().accessorType() + : AccessorType::None; + if (accessorType != AccessorType::None) { + keysOK = false; + break; + } + + if (!isRHSObjLiteralCompatible(value)) { + valuesOK = false; + } + } + + if (propCount > SharedPropMap::MaxPropsForNonDictionary) { + // JSOp::NewObject cannot accept dictionary-mode objects. + keysOK = false; + } + + *withValues = keysOK && valuesOK; + *withoutValues = keysOK; +} + +bool BytecodeEmitter::isArrayObjLiteralCompatible(ListNode* array) { + for (ParseNode* elem : array->contents()) { + if (elem->isKind(ParseNodeKind::Spread)) { + return false; + } + if (!isRHSObjLiteralCompatible(elem)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type) { + // [stack] CTOR? OBJ + + size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is()) { + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as(); + if (field->name().getKind() == ParseNodeKind::ComputedName) { + auto fieldKeys = + field->isStatic() + ? TaggedParserAtomIndex::WellKnown::dot_staticFieldKeys_() + : TaggedParserAtomIndex::WellKnown::dot_fieldKeys_(); + if (!emitGetName(fieldKeys)) { + // [stack] CTOR OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as().kid(); + + if (!emitTree(nameExpr, ValueUsage::WantValue)) { + // [stack] CTOR OBJ ARRAY KEY + return false; + } + + if (!emit1(JSOp::ToPropertyKey)) { + // [stack] CTOR OBJ ARRAY KEY + return false; + } + + size_t fieldKeysIndex; + if (field->isStatic()) { + fieldKeysIndex = curStaticFieldKeyIndex++; + } else { + fieldKeysIndex = curFieldKeyIndex++; + } + + if (!emitUint32Operand(JSOp::InitElemArray, fieldKeysIndex)) { + // [stack] CTOR OBJ ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + } + continue; + } + + if (propdef->isKind(ParseNodeKind::StaticClassBlock)) { + // Static class blocks are emitted as part of + // emitCreateMemberInitializers. + continue; + } + + if (propdef->is()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we + // already handled emitting the constructor, skip it. + MOZ_ASSERT( + propdef->as().scopeBody()->is()); + continue; + } + + // Handle __proto__: v specially because *only* this form, and no other + // involving "__proto__", performs [[Prototype]] mutation. + if (propdef->isKind(ParseNodeKind::MutateProto)) { + // [stack] OBJ + MOZ_ASSERT(type == ObjectLiteral); + if (!pe.prepareForProtoValue(propdef->pn_pos.begin)) { + // [stack] OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } + continue; + } + + if (propdef->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(type == ObjectLiteral); + // [stack] OBJ + if (!pe.prepareForSpreadOperand(propdef->pn_pos.begin)) { + // [stack] OBJ OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL + return false; + } + if (!pe.emitSpread()) { + // [stack] OBJ + return false; + } + continue; + } + + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + AccessorType accessorType; + if (prop->is()) { + ClassMethod& method = prop->as(); + accessorType = method.accessorType(); + + if (!method.isStatic() && key->isKind(ParseNodeKind::PrivateName) && + accessorType != AccessorType::None) { + // Private non-static accessors are stamped onto instances from + // initializers; see emitCreateMemberInitializers. + continue; + } + } else if (prop->is()) { + accessorType = prop->as().accessorType(); + } else { + accessorType = AccessorType::None; + } + + auto emitValue = [this, &key, &prop, accessorType, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + ParseNode* propVal = prop->right(); + if (propVal->isDirectRHSAnonFunction()) { + // The following branches except for the last `else` clause emit the + // cases handled in NameResolver::resolveFun (see NameFunctions.cpp) + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::PrivateName) || + key->isKind(ParseNodeKind::StringExpr)) { + auto keyAtom = key->as().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + auto keyAtom = key->as().toAtom(fc, parserAtoms()); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else if (key->isKind(ParseNodeKind::ComputedName) && + (key->as().kid()->isKind( + ParseNodeKind::NumberExpr) || + key->as().kid()->isKind( + ParseNodeKind::StringExpr)) && + accessorType == AccessorType::None) { + ParseNode* keyKid = key->as().kid(); + if (keyKid->isKind(ParseNodeKind::NumberExpr)) { + auto keyAtom = + keyKid->as().toAtom(fc, parserAtoms()); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else { + MOZ_ASSERT(keyKid->isKind(ParseNodeKind::StringExpr)); + auto keyAtom = keyKid->as().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } + } else { + // Either a proper computed property name or a synthetic computed + // property name for BigInt keys. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + FunctionPrefixKind prefix = + accessorType == AccessorType::None ? FunctionPrefixKind::None + : accessorType == AccessorType::Getter ? FunctionPrefixKind::Get + : FunctionPrefixKind::Set; + + if (!emitAnonymousFunctionWithComputedName(propVal, prefix)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } + } else { + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL + return false; + } + } + + if (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + if (!pe.emitInitHomeObject()) { + // [stack] CTOR? OBJ CTOR? KEY? FUN + return false; + } + } + +#ifdef ENABLE_DECORATORS + if (prop->is()) { + ClassMethod& method = prop->as(); + if (method.decorators() && !method.decorators()->empty()) { + DecoratorEmitter::Kind kind; + switch (method.accessorType()) { + case AccessorType::Getter: + kind = DecoratorEmitter::Getter; + break; + case AccessorType::Setter: + kind = DecoratorEmitter::Setter; + break; + case AccessorType::None: + kind = DecoratorEmitter::Method; + break; + } + + if (!method.isStatic()) { + bool hasKeyOnStack = key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::ComputedName); + if (!emitDupAt(hasKeyOnStack ? 4 : 3)) { + // [stack] ADDINIT OBJ KEY? VAL ADDINIT + return false; + } + } else { + // TODO: See bug 1868220 for support for static methods. + // Note: Key will be present if this has a private name. + if (!emit1(JSOp::Undefined)) { + // [stack] CTOR OBJ CTOR KEY? VAL ADDINIT + return false; + } + } + + if (!emit1(JSOp::Swap)) { + // [stack] ADDINIT CTOR? OBJ CTOR? KEY? ADDINIT VAL + return false; + } + + // The decorators are applied to the current value on the stack, + // possibly replacing it. + DecoratorEmitter de(this); + if (!de.emitApplyDecoratorsToElementDefinition( + kind, key, method.decorators(), method.isStatic())) { + // [stack] ADDINIT CTOR? OBJ CTOR? KEY? ADDINIT VAL + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ADDINIT CTOR? OBJ CTOR? KEY? VAL ADDINIT + return false; + } + + if (!emitPopN(1)) { + // [stack] ADDINIT CTOR? OBJ CTOR? KEY? VAL + return false; + } + } + } +#endif + + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + auto keyAtom = key->as().atom(); + + // emitClass took care of constructor already. + if (type == ClassBody && + keyAtom == TaggedParserAtomIndex::WellKnown::constructor() && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + if (!pe.emitInit(accessorType, keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::NumberExpr)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::ComputedName)) { + // Either a proper computed property name or a synthetic computed property + // name for BigInt keys. + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::PrivateName)); + MOZ_ASSERT(type == ClassBody); + + auto* privateName = &key->as(); + + if (kind == PropertyEmitter::Kind::Prototype) { + MOZ_ASSERT(accessorType == AccessorType::None); + if (!pe.prepareForPrivateMethod()) { + // [stack] CTOR OBJ + return false; + } + NameOpEmitter noe(this, privateName->atom(), + NameOpEmitter::Kind::SimpleAssignment); + + // Ensure the NameOp emitter doesn't push an environment onto the stack, + // because that would change the stack location of the home object. + MOZ_ASSERT(noe.loc().kind() == NameLocation::Kind::FrameSlot || + noe.loc().kind() == NameLocation::Kind::EnvironmentCoordinate); + + if (!noe.prepareForRhs()) { + // [stack] CTOR OBJ + return false; + } + if (!emitValue()) { + // [stack] CTOR OBJ METHOD + return false; + } + if (!noe.emitAssignment()) { + // [stack] CTOR OBJ METHOD + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + if (!pe.skipInit()) { + // [stack] CTOR OBJ + return false; + } + continue; + } + + MOZ_ASSERT(kind == PropertyEmitter::Kind::Static); + + // [stack] CTOR OBJ + + if (!pe.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emitGetPrivateName(privateName)) { + // [stack] CTOR OBJ CTOR KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR OBJ CTOR KEY VAL + return false; + } + + if (!pe.emitPrivateStaticMethod(accessorType)) { + // [stack] CTOR OBJ + return false; + } + + if (privateName->privateNameKind() == PrivateNameKind::Setter) { + if (!emitDupAt(1)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emitGetPrivateName(privateName)) { + // [stack] CTOR OBJ CTOR NAME + return false; + } + if (!emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) { + // [stack] CTOR OBJ CTOR NAME FUN + return false; + } + if (!emit1(JSOp::InitHiddenElemGetter)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, JSOp op, + bool useObjLiteralValues) { + ObjLiteralWriter writer; + +#ifdef DEBUG + // In self-hosted JS, we check duplication only on debug build. + mozilla::Maybe> + selfHostedPropNames; + if (emitterMode == BytecodeEmitter::SelfHosting) { + selfHostedPropNames.emplace(); + } +#endif + + if (op == JSOp::Object) { + writer.beginObject(op); + } else { + MOZ_ASSERT(op == JSOp::NewObject); + writer.beginShape(op); + } + + for (ParseNode* propdef : obj->contents()) { + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + + if (key->is()) { + if (emitterMode == BytecodeEmitter::SelfHosting) { + auto propName = key->as().atom(); +#ifdef DEBUG + // Self-hosted JS shouldn't contain duplicate properties. + auto p = selfHostedPropNames->lookupForAdd(propName); + MOZ_ASSERT(!p); + if (!selfHostedPropNames->add(p, propName)) { + js::ReportOutOfMemory(fc); + return false; + } +#endif + writer.setPropNameNoDuplicateCheck(parserAtoms(), propName); + } else { + if (!writer.setPropName(parserAtoms(), key->as().atom())) { + return false; + } + } + } else { + double numValue = key->as().value(); + int32_t i = 0; + DebugOnly numIsInt = + NumberIsInt32(numValue, &i); // checked previously. + MOZ_ASSERT(numIsInt); + MOZ_ASSERT( + ObjLiteralWriter::arrayIndexInRange(i)); // checked previously. + + // Ignore indexed properties if we're not storing property values, and + // rely on InitElem ops to define those. These properties will be either + // dense elements (not possible to represent in the literal's shape) or + // sparse elements (enumerated separately, so this doesn't affect property + // iteration order). + if (!useObjLiteralValues) { + continue; + } + + writer.setPropIndex(i); + } + + if (useObjLiteralValues) { + MOZ_ASSERT(op == JSOp::Object); + ParseNode* value = prop->right(); + if (!emitObjLiteralValue(writer, value)) { + return false; + } + } else { + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + // JSOp::Object may only be used by (top-level) run-once scripts. + MOZ_ASSERT_IF(op == JSOp::Object, + sc->isTopLevelContext() && sc->treatAsRunOnce()); + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern) { + // Note: if we want to squeeze out a little more performance, we could switch + // to the `JSOp::Object` opcode, because the exclusion set object is never + // exposed to the user, so it's safe to bake the object into the bytecode. + constexpr JSOp op = JSOp::NewObject; + + ObjLiteralWriter writer; + writer.beginShape(op); + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + TaggedParserAtomIndex atom; + if (member->isKind(ParseNodeKind::MutateProto)) { + atom = TaggedParserAtomIndex::WellKnown::proto_(); + } else { + ParseNode* key = member->as().left(); + atom = key->as().atom(); + } + + if (!writer.setPropName(parserAtoms(), atom)) { + return false; + } + + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjLiteralArray(ListNode* array) { + MOZ_ASSERT(checkSingletonContext()); + + constexpr JSOp op = JSOp::Object; + + ObjLiteralWriter writer; + writer.beginArray(op); + + writer.beginDenseArrayElements(); + for (ParseNode* elem : array->contents()) { + if (!emitObjLiteralValue(writer, elem)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) { + return value->isKind(ParseNodeKind::NumberExpr) || + value->isKind(ParseNodeKind::TrueExpr) || + value->isKind(ParseNodeKind::FalseExpr) || + value->isKind(ParseNodeKind::NullExpr) || + value->isKind(ParseNodeKind::RawUndefinedExpr) || + value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr); +} + +bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value) { + MOZ_ASSERT(isRHSObjLiteralCompatible(value)); + if (value->isKind(ParseNodeKind::NumberExpr)) { + double numValue = value->as().value(); + int32_t i = 0; + js::Value v; + if (NumberIsInt32(numValue, &i)) { + v.setInt32(i); + } else { + v.setDouble(numValue); + } + if (!writer.propWithConstNumericValue(fc, v)) { + return false; + } + } else if (value->isKind(ParseNodeKind::TrueExpr)) { + if (!writer.propWithTrueValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::FalseExpr)) { + if (!writer.propWithFalseValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::NullExpr)) { + if (!writer.propWithNullValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) { + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr)) { + if (!writer.propWithAtomValue(fc, parserAtoms(), + value->as().atom())) { + return false; + } + } else { + MOZ_CRASH("Unexpected parse node"); + } + return true; +} + +static bool NeedsPrivateBrand(ParseNode* member) { + return member->is() && + member->as().name().isKind(ParseNodeKind::PrivateName) && + !member->as().isStatic(); +} + +mozilla::Maybe BytecodeEmitter::setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + + size_t numFields = 0; + size_t numPrivateInitializers = 0; + bool hasPrivateBrand = false; + for (ParseNode* member : classMembers->contents()) { + if (NeedsFieldInitializer(member, isStatic)) { + numFields++; + } else if (NeedsAccessorInitializer(member, isStatic)) { + numPrivateInitializers++; + hasPrivateBrand = true; + } else if (NeedsPrivateBrand(member)) { + hasPrivateBrand = true; + } + } + + // If there are more initializers than can be represented, return invalid. + if (numFields + numPrivateInitializers > + MemberInitializers::MaxInitializers) { + return Nothing(); + } + return Some( + MemberInitializers(hasPrivateBrand, numFields + numPrivateInitializers)); +} + +// Purpose of .fieldKeys: +// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, +// not object construction time. The transformation to do so is roughly as +// follows: +// +// class C { +// [keyExpr] = valueExpr; +// } +// --> +// let .fieldKeys = [keyExpr]; +// let .initializers = [ +// () => { +// this[.fieldKeys[0]] = valueExpr; +// } +// ]; +// class C { +// constructor() { +// .initializers[0](); +// } +// } +// +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` +// BytecodeEmitter::emitPropertyList fills in the elements of the array. +// See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part. +bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + auto isFieldWithComputedName = [isStatic](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() == isStatic && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + size_t numFieldKeys = std::count_if( + obj->contents().begin(), obj->contents().end(), isFieldWithComputedName); + if (numFieldKeys == 0) { + return true; + } + + auto fieldKeys = + isStatic ? TaggedParserAtomIndex::WellKnown::dot_staticFieldKeys_() + : TaggedParserAtomIndex::WellKnown::dot_fieldKeys_(); + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) { + // [stack] ARRAY + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +static bool HasInitializer(ParseNode* node, bool isStaticContext) { + return (node->is() && + node->as().isStatic() == isStaticContext) || + (isStaticContext && node->is()); +} + +static FunctionNode* GetInitializer(ParseNode* node, bool isStaticContext) { + MOZ_ASSERT(HasInitializer(node, isStaticContext)); + MOZ_ASSERT_IF(!node->is(), isStaticContext); + return node->is() ? node->as().initializer() + : node->as().function(); +} + +static bool IsPrivateInstanceAccessor(const ClassMethod* classMethod) { + return !classMethod->isStatic() && + classMethod->name().isKind(ParseNodeKind::PrivateName) && + classMethod->accessorType() != AccessorType::None; +} + +bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement +#ifdef ENABLE_DECORATORS + , + bool hasHeritage +#endif +) { + // FieldPlacement::Instance, hasHeritage == false + // [stack] HOME + // + // FieldPlacement::Instance, hasHeritage == true + // [stack] HOME HERIT + // + // FieldPlacement::Static + // [stack] CTOR HOME +#ifdef ENABLE_DECORATORS + MOZ_ASSERT_IF(placement == FieldPlacement::Static, !hasHeritage); +#endif + mozilla::Maybe memberInitializers = + setupMemberInitializers(obj, placement); + if (!memberInitializers) { + ReportAllocationOverflow(fc); + return false; + } + + size_t numInitializers = memberInitializers->numMemberInitializers; + if (numInitializers == 0) { + return true; + } + + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForMemberInitializers(numInitializers, isStatic)) { + // [stack] HOME HERIT? ARR + // or: + // [stack] CTOR HOME ARR + return false; + } + + // Private accessors could be used in the field initializers, so make sure + // accessor initializers appear earlier in the .initializers array so they + // run first. Static private methods are not initialized using initializers + // (emitPropertyList emits bytecode to stamp them onto the constructor), so + // skip this step if isStatic. + if (!isStatic) { + if (!emitPrivateMethodInitializers(ce, obj)) { + return false; + } + } + + for (ParseNode* propdef : obj->contents()) { + if (!HasInitializer(propdef, isStatic)) { + continue; + } + + FunctionNode* initializer = GetInitializer(propdef, isStatic); + + if (!ce.prepareForMemberInitializer()) { + return false; + } + if (!emitTree(initializer)) { + // [stack] HOME HERIT? ARR LAMBDA + // or: + // [stack] CTOR HOME ARR LAMBDA + return false; + } + if (initializer->funbox()->needsHomeObject()) { + MOZ_ASSERT(initializer->funbox()->allowSuperProperty()); + if (!ce.emitMemberInitializerHomeObject(isStatic)) { + // [stack] HOME HERIT? ARR LAMBDA + // or: + // [stack] CTOR HOME ARR LAMBDA + return false; + } + } + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOME HERIT? ARR + // or: + // [stack] CTOR HOME ARR + return false; + } + } + +#ifdef ENABLE_DECORATORS + // Index to use to append new initializers returned by decorators to the array + if (!emitNumberOp(numInitializers)) { + // [stack] HOME HERIT? ARR I + // or: + // [stack] CTOR HOME ARR I + return false; + } + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + continue; + } + ClassField* field = &propdef->as(); + if (field->isStatic() != isStatic) { + continue; + } + if (field->decorators() && !field->decorators()->empty()) { + DecoratorEmitter de(this); + if (!field->hasAccessor()) { + if (!emitDupAt((hasHeritage || isStatic) ? 4 : 3)) { + // [stack] ADDINIT HOME HERIT? ARR I ADDINIT + // or: + // [stack] ADDINIT CTOR HOME ARR I ADDINIT + return false; + } + if (!de.emitApplyDecoratorsToFieldDefinition( + &field->name(), field->decorators(), field->isStatic())) { + // [stack] HOME HERIT? ARR I ADDINIT INITS + // or: + // [stack] CTOR HOME ARR I ADDINIT INITS + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] HOME HERIT? ARR I INITS ADDINIT + // or: + // [stack] CTOR HOME ARR I INITS ADDINIT + return false; + } + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS + return false; + } + } else { + ClassMethod* accessorGetterNode = field->accessorGetterNode(); + auto accessorGetterKeyAtom = + accessorGetterNode->left()->as().atom(); + ClassMethod* accessorSetterNode = field->accessorSetterNode(); + auto accessorSetterKeyAtom = + accessorSetterNode->left()->as().atom(); + if (!IsPrivateInstanceAccessor(accessorGetterNode)) { + if (!emitTree(&accessorGetterNode->method())) { + // [stack] ADDINIT HOME HERIT? ARR I GET + // or: + // [stack] ADDINIT CTOR HOME ARR I GET + return false; + } + if (!emitTree(&accessorSetterNode->method())) { + // [stack] ADDINIT HOME HERIT? ARR I GET + // SET + // or: + // [stack] ADDINIT CTOR HOME ARR I GET SET + return false; + } + } else { + MOZ_ASSERT(IsPrivateInstanceAccessor(accessorSetterNode)); + auto getAccessor = [this]( + ClassMethod* classMethod, + TaggedParserAtomIndex& updatedAtom) -> bool { + // [stack] + + // Synthesize a name for the lexical variable that will store the + // private method body. + TaggedParserAtomIndex name = + classMethod->name().as().atom(); + AccessorType accessorType = classMethod->accessorType(); + StringBuffer storedMethodName(fc); + if (!storedMethodName.append(parserAtoms(), name)) { + return false; + } + if (!storedMethodName.append(accessorType == AccessorType::Getter + ? ".getter" + : ".setter")) { + return false; + } + updatedAtom = storedMethodName.finishParserAtom(parserAtoms(), fc); + if (!updatedAtom) { + return false; + } + + return emitGetName(updatedAtom); + // [stack] ACCESSOR + }; + + if (!getAccessor(accessorGetterNode, accessorGetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I GET + // or: + // [stack] ADDINIT CTOR HOME ARR I GET + return false; + }; + + if (!getAccessor(accessorSetterNode, accessorSetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I GET SET + // or: + // [stack] ADDINIT CTOR HOME ARR I GET SET + return false; + }; + } + + if (!emitDupAt((hasHeritage || isStatic) ? 6 : 5)) { + // [stack] ADDINIT HOME HERIT? ARR I GET SET ADDINIT + // or: + // [stack] ADDINIT CTOR HOME ARR I GET SET ADDINIT + return false; + } + + if (!emitUnpickN(2)) { + // [stack] ADDINIT HOME HERIT? ARR I ADDINIT GET SET + // or: + // [stack] ADDINIT CTOR HOME ARR I ADDINIT GET SET + return false; + } + + if (!de.emitApplyDecoratorsToAccessorDefinition( + &field->name(), field->decorators(), field->isStatic())) { + // [stack] ADDINIT HOME HERIT? ARR I ADDINIT GET SET INITS + // or: + // [stack] ADDINIT CTOR HOME ARR I ADDINIT GET SET INITS + return false; + } + + if (!emitPickN(3)) { + // [stack] HOME HERIT? ARR I GET SET INITS ADDINIT + // or: + // [stack] CTOR HOME ARR I GET SET INITS ADDINIT + return false; + } + + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR I GET SET INITS + // or: + // [stack] ADDINIT CTOR HOME ARR I GET SET INITS + return false; + } + + if (!emitUnpickN(2)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET SET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET + return false; + } + + if (!IsPrivateInstanceAccessor(accessorGetterNode)) { + if (!isStatic) { + if (!emitDupAt(hasHeritage ? 6 : 5)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET SET HOME + return false; + } + } else { + if (!emitDupAt(6)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET CTOR + return false; + } + if (!emitDupAt(6)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET CTOR HOME + return false; + } + } + + PropertyEmitter::Kind kind = field->isStatic() + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (!accessorGetterNode->name().isKind(ParseNodeKind::PrivateName)) { + MOZ_ASSERT( + !accessorSetterNode->name().isKind(ParseNodeKind::PrivateName)); + + if (!ce.prepareForPropValue(propdef->pn_pos.begin, kind)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET SET HOME + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET CTOR HOME CTOR + return false; + } + if (!emitPickN(isStatic ? 3 : 1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET HOME SET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME CTOR SET + return false; + } + if (!ce.emitInit(AccessorType::Setter, accessorSetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET HOME + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME + return false; + } + + if (!ce.prepareForPropValue(propdef->pn_pos.begin, kind)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET HOME + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME CTOR + return false; + } + if (!emitPickN(isStatic ? 3 : 1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS HOME GET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS CTOR HOME CTOR GET + return false; + } + if (!ce.emitInit(AccessorType::Getter, accessorGetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS HOME + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS CTOR HOME + return false; + } + } else { + MOZ_ASSERT(isStatic); + // The getter and setter share the same name. + if (!emitNewPrivateName(accessorSetterKeyAtom, + accessorSetterKeyAtom)) { + return false; + } + if (!ce.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET CTOR HOME CTOR + return false; + } + if (!emitGetPrivateName( + &accessorSetterNode->name().as())) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET CTOR HOME CTOR + // KEY + return false; + } + if (!emitPickN(4)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME CTOR KEY + // SET + return false; + } + if (!ce.emitPrivateStaticMethod(AccessorType::Setter)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME + return false; + } + + if (!ce.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME CTOR + return false; + } + if (!emitGetPrivateName( + &accessorGetterNode->name().as())) { + // [stack] ADDINIT CTOR HOME ARR I INITS GET CTOR HOME CTOR KEY + return false; + } + if (!emitPickN(4)) { + // [stack] ADDINIT CTOR HOME ARR I INITS CTOR HOME CTOR KEY GET + return false; + } + if (!ce.emitPrivateStaticMethod(AccessorType::Getter)) { + // [stack] ADDINIT CTOR HOME ARR I INITS CTOR HOME + return false; + } + } + + if (!isStatic) { + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS + return false; + } + } else { + if (!emitPopN(2)) { + // [stack] ADDINIT CTOR HOME ARR I INITS + return false; + } + } + } else { + MOZ_ASSERT(IsPrivateInstanceAccessor(accessorSetterNode)); + + if (!emitLexicalInitialization(accessorSetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET SET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET SET + return false; + } + + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET + return false; + } + + if (!emitLexicalInitialization(accessorGetterKeyAtom)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS GET + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS GET + return false; + } + + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR I INITS + // or: + // [stack] ADDINIT CTOR HOME ARR I INITS + return false; + } + } + } + if (!emit1(JSOp::InitElemInc)) { + // [stack] ADDINIT HOME HERIT? ARR I + // or: + // [stack] ADDINIT CTOR HOME ARR I + return false; + } + } + } + + // Pop I + if (!emitPopN(1)) { + // [stack] ADDINIT HOME HERIT? ARR + // or: + // [stack] ADDINIT CTOR HOME ARR + return false; + } +#endif + + if (!ce.emitMemberInitializersEnd()) { + // [stack] ADDINIT HOME HERIT? + // or: + // [stack] ADDINIT CTOR HOME + return false; + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj) { + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + continue; + } + auto* classMethod = &propdef->as(); + + // Skip over anything which isn't a private instance accessor. + if (!IsPrivateInstanceAccessor(classMethod)) { + continue; + } + + if (!ce.prepareForMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Synthesize a name for the lexical variable that will store the + // private method body. + TaggedParserAtomIndex name = classMethod->name().as().atom(); + AccessorType accessorType = classMethod->accessorType(); + StringBuffer storedMethodName(fc); + if (!storedMethodName.append(parserAtoms(), name)) { + return false; + } + if (!storedMethodName.append( + accessorType == AccessorType::Getter ? ".getter" : ".setter")) { + return false; + } + auto storedMethodAtom = + storedMethodName.finishParserAtom(parserAtoms(), fc); + + // Emit the private method body and store it as a lexical var. + if (!emitFunction(&classMethod->method())) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + // The private method body needs to access the home object, + // and the CE knows where that is on the stack. + if (classMethod->method().funbox()->needsHomeObject()) { + if (!ce.emitMemberInitializerHomeObject(false)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + } + if (!emitLexicalInitialization(storedMethodAtom)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + if (!emitPrivateMethodInitializer(classMethod, storedMethodAtom)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Store the emitted initializer function into the .initializers array. + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializer( + ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom) { + MOZ_ASSERT(IsPrivateInstanceAccessor(classMethod)); + + auto* name = &classMethod->name().as(); + + // Emit the synthesized initializer function. + FunctionNode* funNode = classMethod->initializerIfPrivate(); + MOZ_ASSERT(funNode); + FunctionBox* funbox = funNode->funbox(); + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + FunctionEmitter::IsHoisted::No); + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, funbox); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + ParamsBodyNode* paramsBody = funNode->body(); + FunctionScriptEmitter fse(&bce2, funbox, Nothing(), Nothing()); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + if (!bce2.emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!bce2.emit1(JSOp::FunctionThis)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(name)) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitGetName(storedMethodAtom)) { + // [stack] THIS NAME METHOD + return false; + } + + switch (name->privateNameKind()) { + case PrivateNameKind::Setter: + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(name)) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitAtomOp( + JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) { + // [stack] THIS NAME FUN + return false; + } + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Getter: + case PrivateNameKind::GetterSetter: + if (classMethod->accessorType() == AccessorType::Getter) { + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + } else { + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + // Pop remaining THIS. + if (!bce2.emit1(JSOp::Pop)) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + if (!fse.intoStencil()) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] HOMEOBJ HERITAGE? ARRAY FUN + // or: + // [stack] CTOR HOMEOBJ ARRAY FUN + return false; + } + + return true; +} + +const MemberInitializers& BytecodeEmitter::findMemberInitializersForCall() { + for (BytecodeEmitter* current = this; current; current = current->parent) { + if (current->sc->isFunctionBox()) { + FunctionBox* funbox = current->sc->asFunctionBox(); + + if (funbox->isArrow()) { + continue; + } + + // If we found a non-arrow / non-constructor we were never allowed to + // expect fields in the first place. + MOZ_RELEASE_ASSERT(funbox->isClassConstructor()); + + return funbox->useMemberInitializers() ? funbox->memberInitializers() + : MemberInitializers::Empty(); + } + } + + MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers); + return *compilationState.scopeContext.memberInitializers; +} + +bool BytecodeEmitter::emitInitializeInstanceMembers( + bool isDerivedClassConstructor) { + const MemberInitializers& memberInitializers = + findMemberInitializersForCall(); + MOZ_ASSERT(memberInitializers.valid); + + if (memberInitializers.hasPrivateBrand) { + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_this_())) { + // [stack] THIS + return false; + } + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_privateBrand_())) { + // [stack] THIS BRAND + return false; + } + if (isDerivedClassConstructor) { + if (!emitCheckPrivateField(ThrowCondition::ThrowHas, + ThrowMsgKind::PrivateBrandDoubleInit)) { + // [stack] THIS BRAND BOOL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] THIS BRAND + return false; + } + } + if (!emit1(JSOp::Null)) { + // [stack] THIS BRAND NULL + return false; + } + if (!emit1(JSOp::InitHiddenElem)) { + // [stack] THIS + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + size_t numInitializers = memberInitializers.numMemberInitializers; + if (numInitializers > 0) { + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_initializers_())) { + // [stack] ARRAY + return false; + } + + for (size_t index = 0; index < numInitializers; index++) { + if (index < numInitializers - 1) { + // We Dup to keep the array around (it is consumed in the bytecode + // below) for next iterations of this loop, except for the last + // iteration, which avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(index)) { + // [stack] ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_this_())) { + // [stack] ARRAY? FUNC THIS + return false; + } + + // Callee is always internal function. + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY? + return false; + } + } +#ifdef ENABLE_DECORATORS + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-initializeinstanceelements + // 4. For each element e of elements, do + // 4.a. If elementRecord.[[Kind]] is field or accessor, then + // 4.a.i. Perform ? InitializeFieldOrAccessor(O, elementRecord). + // + + // TODO: (See Bug 1817993) At the moment, we're applying the initialization + // logic in two steps. The pre-decorator initialization code runs, stores + // the initial value, and then we retrieve it here and apply the + // initializers added by decorators. We should unify these two steps. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_initializers_())) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY ARRAY + return false; + } + + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::length())) { + // [stack] ARRAY LENGTH + return false; + } + + if (!emitNumberOp(static_cast(numInitializers))) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + InternalWhileEmitter wh(this); + // At this point, we have no context to determine offsets in the + // code for this while statement. Ideally, it would correspond to + // the field we're initializing. + if (!wh.emitCond()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY LENGTH INDEX INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] ARRAY LENGTH INDEX INDEX LENGTH + return false; + } + + if (!emit1(JSOp::Lt)) { + // [stack] ARRAY LENGTH INDEX BOOL + return false; + } + + if (!wh.emitBody()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] ARRAY LENGTH INDEX ARRAY + return false; + } + + if (!emitDupAt(1)) { + // [stack] ARRAY LENGTH INDEX ARRAY INDEX + return false; + } + + // Retrieve initializers for this field + if (!emit1(JSOp::GetElem)) { + // [stack] ARRAY LENGTH INDEX INITIALIZERS + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dot_this_())) { + // [stack] ARRAY LENGTH INDEX INITIALIZERS THIS + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ARRAY LENGTH INDEX THIS INITIALIZERS + return false; + } + + DecoratorEmitter de(this); + if (!de.emitInitializeFieldOrAccessor()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Inc)) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!wh.emitEnd()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emitPopN(3)) { + // [stack] + return false; + } + // 5. Return unused. + + if (!de.emitCallExtraInitializers(TaggedParserAtomIndex::WellKnown:: + dot_instanceExtraInitializers_())) { + return false; + } +#endif + } + return true; +} + +bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { + auto isStaticField = [](ParseNode* propdef) { + return HasInitializer(propdef, true); + }; + size_t numFields = + std::count_if(classMembers->contents().begin(), + classMembers->contents().end(), isStaticField); + + if (numFields == 0) { + return true; + } + + if (!emitGetName( + TaggedParserAtomIndex::WellKnown::dot_staticInitializers_())) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (hasNext) { + // We Dup to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + // Callee is always internal function. + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] CTOR ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR ARRAY? + return false; + } + } + +#ifdef ENABLE_DECORATORS + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-initializeinstanceelements + // 4. For each element e of elements, do + // 4.a. If elementRecord.[[Kind]] is field or accessor, then + // 4.a.i. Perform ? InitializeFieldOrAccessor(O, elementRecord). + // + + // TODO: (See Bug 1817993) At the moment, we're applying the initialization + // logic in two steps. The pre-decorator initialization code runs, stores + // the initial value, and then we retrieve it here and apply the + // initializers added by decorators. We should unify these two steps. + if (!emitGetName( + TaggedParserAtomIndex::WellKnown::dot_staticInitializers_())) { + // [stack] CTOR ARRAY + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::length())) { + // [stack] CTOR ARRAY LENGTH + return false; + } + + if (!emitNumberOp(static_cast(numFields))) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + InternalWhileEmitter wh(this); + // At this point, we have no context to determine offsets in the + // code for this while statement. Ideally, it would correspond to + // the field we're initializing. + if (!wh.emitCond()) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] CTOR ARRAY LENGTH INDEX INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] CTOR ARRAY LENGTH INDEX INDEX LENGTH + return false; + } + + if (!emit1(JSOp::Lt)) { + // [stack] CTOR ARRAY LENGTH INDEX BOOL + return false; + } + + if (!wh.emitBody()) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] CTOR ARRAY LENGTH INDEX ARRAY + return false; + } + + if (!emitDupAt(1)) { + // [stack] CTOR ARRAY LENGTH INDEX ARRAY INDEX + return false; + } + + // Retrieve initializers for this field + if (!emit1(JSOp::GetElem)) { + // [stack] CTOR ARRAY LENGTH INDEX INITIALIZERS + return false; + } + + if (!emitDupAt(4)) { + // [stack] CTOR ARRAY LENGTH INDEX INITIALIZERS CTOR + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] CTOR ARRAY LENGTH INDEX CTOR INITIALIZERS + return false; + } + + DecoratorEmitter de(this); + if (!de.emitInitializeFieldOrAccessor()) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Inc)) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + if (!wh.emitEnd()) { + // [stack] CTOR ARRAY LENGTH INDEX + return false; + } + + if (!emitPopN(3)) { + // [stack] CTOR + return false; + } + // 5. Return unused. +#endif + + // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to + // avoid keeping the arrays alive indefinitely. + auto clearStaticFieldSlot = [&](TaggedParserAtomIndex name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? VAL? UNDEFINED + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + if (!clearStaticFieldSlot( + TaggedParserAtomIndex::WellKnown::dot_staticInitializers_())) { + return false; + } + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && propdef->as().isStatic() && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + if (std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), + isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot( + TaggedParserAtomIndex::WellKnown::dot_staticFieldKeys_())) { + 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) { + // Note: this method uses the ObjLiteralWriter and emits ObjLiteralStencil + // objects into the GCThingList, which will evaluate them into real GC objects + // or shapes during JSScript::fullyInitFromEmitter. Eventually we want + // JSOp::Object to be a real opcode, but for now, performance constraints + // limit us to evaluating object literals at the end of parse, when we're + // allowed to allocate GC things. + // + // There are four cases here, in descending order of preference: + // + // 1. The list of property names is "normal" and constant (no computed + // values, no integer indices), the values are all simple constants + // (numbers, booleans, strings), *and* this occurs in a run-once + // (singleton) context. In this case, we can emit ObjLiteral + // instructions to build an object with values, and the object will be + // attached to a JSOp::Object opcode, whose semantics are for the backend + // to simply steal the object from the script. + // + // 2. The list of property names is "normal" and constant as above, *and* this + // occurs in a run-once (singleton) context, but some values are complex + // (computed expressions, sub-objects, functions, etc.). In this case, we + // can still use JSOp::Object (because singleton context), but the object + // has |undefined| property values and InitProp ops are emitted to set the + // values. + // + // 3. The list of property names is "normal" and constant as above, but this + // occurs in a non-run-once (non-singleton) context. In this case, we can + // use the ObjLiteral functionality to describe an *empty* object (all + // values left undefined) with the right fields, which will become a + // JSOp::NewObject opcode using the object's shape to speed up the creation + // of the object each time it executes. The emitted bytecode still needs + // InitProp ops to set the values in this case. + // + // 4. Any other case. As a fallback, we use NewInit to create a new, empty + // object (i.e., `{}`) and then emit bytecode to initialize its properties + // one-by-one. + + bool useObjLiteral = false; + bool useObjLiteralValues = false; + isPropertyListObjLiteralCompatible(objNode, &useObjLiteralValues, + &useObjLiteral); + + // [stack] + // + ObjectEmitter oe(this); + if (useObjLiteral) { + bool singleton = checkSingletonContext() && + !objNode->hasNonConstInitializer() && objNode->head(); + JSOp op; + if (singleton) { + // Case 1 or 2. + op = JSOp::Object; + } else { + // Case 3. + useObjLiteralValues = false; + op = JSOp::NewObject; + } + + // Use an ObjLiteral op. This will record ObjLiteral insns in the + // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral + // fixups so that at GC-publish time at the end of parse, the full object + // (case 1 or 2) or shape (case 3) can be allocated and the bytecode can be + // patched to refer to it. + if (!emitPropertyListObjLiteral(objNode, op, useObjLiteralValues)) { + // [stack] OBJ + return false; + } + // Put the ObjectEmitter in the right state. This tells it that there will + // already be an object on the stack as a result of the (eventual) + // NewObject or Object op, and prepares it to emit values if needed. + if (!oe.emitObjectWithTemplateOnStack()) { + // [stack] OBJ + return false; + } + if (!useObjLiteralValues) { + // Case 2 or 3 above: we still need to emit bytecode to fill in the + // object's property values. + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + } else { + // Case 4 above: no ObjLiteral use, just bytecode to build the object from + // scratch. + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + + if (!oe.emitEnd()) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitArrayLiteral(ListNode* array) { + // Emit JSOp::Object if the array consists entirely of primitive values and we + // are in a singleton context. + if (checkSingletonContext() && !array->hasNonConstInitializer() && + !array->empty() && isArrayObjLiteralCompatible(array)) { + return emitObjLiteralArray(array); + } + + return emitArray(array); +} + +bool BytecodeEmitter::emitArray(ListNode* array) { + /* + * 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::InitElemArray 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 : array->contents()) { + 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_NewArray, " + "BaselineCompiler::emit_InitElemArray, " + "and DoSetElemFallback's handling of JSOp::InitElemArray"); + + uint32_t count = array->count(); + 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)) { + // [stack] ARRAY + return false; + } + + uint32_t index = 0; + bool afterSpread = false; + for (ParseNode* elem : array->contents()) { + if (elem->isKind(ParseNodeKind::Spread)) { + if (!afterSpread) { + afterSpread = true; + if (!emitNumberOp(index)) { + // [stack] ARRAY INDEX + return false; + } + } + + ParseNode* expr = elem->as().kid(); + SelfHostedIter selfHostedIter = getSelfHostedIterFor(expr); + + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + return false; + } + if (!emitIterable(expr, selfHostedIter)) { + // [stack] ARRAY INDEX ITERABLE + return false; + } + if (!emitIterator(selfHostedIter)) { + // [stack] ARRAY INDEX NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] INDEX NEXT ITER ARRAY + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread(selfHostedIter)) { + // [stack] ARRAY INDEX + return false; + } + } else { + if (!updateSourceCoordNotesIfNonLiteral(elem)) { + return false; + } + if (elem->isKind(ParseNodeKind::Elision)) { + if (!emit1(JSOp::Hole)) { + return false; + } + } else { + if (!emitTree(elem, ValueUsage::WantValue)) { + // [stack] ARRAY INDEX? VALUE + return false; + } + } + + if (afterSpread) { + if (!emit1(JSOp::InitElemInc)) { + // [stack] ARRAY (INDEX+1) + return false; + } + } else { + if (!emitUint32Operand(JSOp::InitElemArray, index)) { + // [stack] ARRAY + return false; + } + } + } + + index++; + } + MOZ_ASSERT(index == count); + if (afterSpread) { + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitSpreadIntoArray(UnaryNode* elem) { + MOZ_ASSERT(elem->isKind(ParseNodeKind::Spread)); + + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + // [stack] VALUE + return false; + } + + SelfHostedIter selfHostedIter = getSelfHostedIterFor(elem->kid()); + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Deny || + selfHostedIter == SelfHostedIter::AllowContent); + + if (!emitIterator(selfHostedIter)) { + // [stack] NEXT ITER + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] NEXT ITER ARRAY + return false; + } + + if (!emitNumberOp(0)) { + // [stack] NEXT ITER ARRAY INDEX + return false; + } + + if (!emitSpread(selfHostedIter)) { + // [stack] ARRAY INDEX + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + return true; +} + +#ifdef ENABLE_RECORD_TUPLE +bool BytecodeEmitter::emitRecordLiteral(ListNode* record) { + if (!emitUint32Operand(JSOp::InitRecord, record->count())) { + // [stack] RECORD + return false; + } + + for (ParseNode* propdef : record->contents()) { + if (propdef->isKind(ParseNodeKind::Spread)) { + if (!emitTree(propdef->as().kid())) { + // [stack] RECORD SPREADEE + return false; + } + if (!emit1(JSOp::AddRecordSpread)) { + // [stack] RECORD + return false; + } + } else { + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + ParseNode* value = prop->right(); + + switch (key->getKind()) { + case ParseNodeKind::ObjectPropertyName: + if (!emitStringOp(JSOp::String, key->as().atom())) { + return false; + } + break; + case ParseNodeKind::ComputedName: + if (!emitTree(key->as().kid())) { + return false; + } + break; + default: + MOZ_ASSERT(key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)); + if (!emitTree(key)) { + return false; + } + break; + } + // [stack] RECORD KEY + + if (!emitTree(value)) { + // [stack] RECORD KEY VALUE + return false; + } + + if (!emit1(JSOp::AddRecordProperty)) { + // [stack] RECORD + return false; + } + } + } + + if (!emit1(JSOp::FinishRecord)) { + // [stack] RECORD + return false; + } + + return true; +} + +bool BytecodeEmitter::emitTupleLiteral(ListNode* tuple) { + if (!emitUint32Operand(JSOp::InitTuple, tuple->count())) { + // [stack] TUPLE + return false; + } + + for (ParseNode* elt : tuple->contents()) { + if (elt->isKind(ParseNodeKind::Spread)) { + ParseNode* expr = elt->as().kid(); + auto selfHostedIter = getSelfHostedIterFor(expr); + + if (!emitIterable(expr, selfHostedIter)) { + // [stack] TUPLE ITERABLE + return false; + } + if (!emitIterator(selfHostedIter)) { + // [stack] TUPLE NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER TUPLE + return false; + } + if (!emitSpread(selfHostedIter, /* spreadeeStackItems = */ 1, + JSOp::AddTupleElement)) { + // [stack] TUPLE + return false; + } + } else { + // Update location to throw errors about non-primitive elements + // in the correct position. + if (!updateSourceCoordNotesIfNonLiteral(elt)) { + return false; + } + + if (!emitTree(elt)) { + // [stack] TUPLE VALUE + return false; + } + + if (!emit1(JSOp::AddTupleElement)) { + // [stack] TUPLE + return false; + } + } + } + + if (!emit1(JSOp::FinishTuple)) { + // [stack] TUPLE + return false; + } + + return true; +} +#endif + +static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::ThrowStmt: + return JSOp::Throw; + case ParseNodeKind::VoidExpr: + return JSOp::Void; + case ParseNodeKind::NotExpr: + return JSOp::Not; + case ParseNodeKind::BitNotExpr: + return JSOp::BitNot; + case ParseNodeKind::PosExpr: + return JSOp::Pos; + case ParseNodeKind::NegExpr: + return JSOp::Neg; + default: + MOZ_CRASH("unexpected unary op"); + } +} + +bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) { + if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) { + return false; + } + + JSOp op = UnaryOpParseNodeKindToJSOp(unaryNode->getKind()); + ValueUsage valueUsage = + op == JSOp::Void ? ValueUsage::IgnoreValue : ValueUsage::WantValue; + if (!emitTree(unaryNode->kid(), valueUsage)) { + return false; + } + return emit1(op); +} + +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::emitFunctionFormalParameters(ParamsBodyNode* paramsBody) { + FunctionBox* funbox = sc->asFunctionBox(); + + bool hasRest = funbox->hasRest(); + + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg : paramsBody->parameters()) { + ParseNode* bindingElement = arg; + ParseNode* initializer = nullptr; + if (arg->isKind(ParseNodeKind::AssignExpr)) { + bindingElement = arg->as().left(); + initializer = arg->as().right(); + } + bool hasInitializer = !!initializer; + bool isRest = + hasRest && arg->pn_next == *std::end(paramsBody->parameters()); + bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name); + + // Left-hand sides are either simple names or destructuring patterns. + MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) || + bindingElement->isKind(ParseNodeKind::ArrayExpr) || + bindingElement->isKind(ParseNodeKind::ObjectExpr)); + + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] + + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; + + auto emitDestructuring = [this, &bindingElement]() { + // [stack] ARG + + if (!this->emitDestructuringOps(&bindingElement->as(), + DestructuringFlavor::Declaration)) { + // [stack] ARG + return false; + } + + return true; + }; + + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + auto paramName = bindingElement->as().name(); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; + } + } + + continue; + } + + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + auto paramName = bindingElement->as().name(); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + auto paramName = bindingElement->as().name(); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { + FunctionBox* funbox = sc->asFunctionBox(); + + // [stack] + + auto emitInitializeFunctionSpecialName = + [](BytecodeEmitter* bce, TaggedParserAtomIndex 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()) { + // [stack] + return false; + } + if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS/NEW.TARGET + return false; + } + if (!noe.emitAssignment()) { + // [stack] THIS/ARGUMENTS/NEW.TARGET + return false; + } + if (!bce->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + // Do nothing if the function doesn't have an arguments binding. + if (funbox->needsArgsObj()) { + // Self-hosted code should use the more efficient ArgumentsLength and + // GetArgument intrinsics instead of `arguments`. + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::arguments(), + JSOp::Arguments)) { + // [stack] + 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->functionHasThisBinding()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dot_this_(), + JSOp::FunctionThis)) { + return false; + } + } + + // Do nothing if the function doesn't have a new.target-binding (this happens + // for instance if it doesn't use new.target/eval or if it's an arrow + // function). + if (funbox->functionHasNewTargetBinding()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dot_newTarget_(), + JSOp::NewTarget)) { + return false; + } + } + + // Do nothing if the function doesn't implicitly return a promise result. + if (funbox->needsPromiseResult()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dot_generator_(), + JSOp::Generator)) { + // [stack] + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { + return emitLexicalInitialization(name->name()); +} + +bool BytecodeEmitter::emitLexicalInitialization(TaggedParserAtomIndex name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + // The caller has pushed the RHS to the top of the stack. Assert that the + // binding can be initialized without a binding object on the stack, and that + // no JSOp::BindName or JSOp::BindGName ops were emitted. + MOZ_ASSERT(noe.loc().isLexical() || noe.loc().isSynthetic() || + noe.loc().isPrivateMethod()); + MOZ_ASSERT(!noe.emittedBindOp()); + + if (!noe.emitAssignment()) { + return false; + } + + return true; +} + +static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(ListNode* classMethods) { + for (ParseNode* classElement : classMethods->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is()) { + unwrappedElement = unwrappedElement->as().scopeBody(); + } + if (unwrappedElement->is()) { + ClassMethod& method = unwrappedElement->as(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(ParseNodeKind::ObjectPropertyName) || + methodName.isKind(ParseNodeKind::StringExpr)) && + methodName.as().atom() == + TaggedParserAtomIndex::WellKnown::constructor()) { + return classElement; + } + } + } + return nullptr; +} + +bool BytecodeEmitter::emitNewPrivateName(TaggedParserAtomIndex bindingName, + TaggedParserAtomIndex symbolName) { + if (!emitAtomOp(JSOp::NewPrivateName, symbolName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Add a binding for #name => privatename + if (!emitLexicalInitialization(bindingName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Pop Private name off the stack. + if (!emit1(JSOp::Pop)) { + // [stack] HERITAGE + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNewPrivateNames( + TaggedParserAtomIndex privateBrandName, ListNode* classMembers) { + bool hasPrivateBrand = false; + + for (ParseNode* classElement : classMembers->contents()) { + ParseNode* elementName; + if (classElement->is()) { + elementName = &classElement->as().name(); + } else if (classElement->is()) { + elementName = &classElement->as().name(); + } else { + continue; + } + + if (!elementName->isKind(ParseNodeKind::PrivateName)) { + continue; + } + + // Non-static private methods' private names are optimized away. + bool isOptimized = false; + if (classElement->is() && + !classElement->as().isStatic()) { + hasPrivateBrand = true; + if (classElement->as().accessorType() == + AccessorType::None) { + isOptimized = true; + } + } + + if (!isOptimized) { + auto privateName = elementName->as().name(); + if (!emitNewPrivateName(privateName, privateName)) { + return false; + } + } + } + + if (hasPrivateBrand) { + // We don't make a private name for every optimized method, but we need one + // private name per class, the `.privateBrand`. + if (!emitNewPrivateName( + TaggedParserAtomIndex::WellKnown::dot_privateBrand_(), + privateBrandName)) { + return false; + } + } + return true; +} + +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool BytecodeEmitter::emitClass( + ClassNode* classNode, + ClassNameKind nameKind /* = ClassNameKind::BindingName */, + TaggedParserAtomIndex + nameForAnonymousClass /* = TaggedParserAtomIndex::null() */) { + MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == + bool(nameForAnonymousClass)); + + ParseNode* heritageExpression = classNode->heritage(); + ListNode* classMembers = classNode->memberList(); + ParseNode* constructor = FindConstructor(classMembers); + + // If |nameKind != ClassNameKind::ComputedName| + // [stack] + // Else + // [stack] NAME + + ClassEmitter ce(this); + TaggedParserAtomIndex innerName; + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; + if (ClassNames* names = classNode->names()) { + MOZ_ASSERT(nameKind == ClassNameKind::BindingName); + innerName = names->innerBinding()->name(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->name()); + MOZ_ASSERT(names->outerBinding()->name() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + } + + if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { + if (!ce.emitScope(scopeBindings->scopeBindings())) { + // [stack] + return false; + } + } + + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!updateSourceCoordNotes(classNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE + return false; + } + } + + // The class body scope holds any private names. Those mustn't be visible in + // the heritage expression and hence the scope must be emitted after the + // heritage expression. + if (ClassBodyScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) { + if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) { + // [stack] HERITAGE + return false; + } + + // The spec does not say anything about private brands being symbols. It's + // an implementation detail. So we can give the special private brand + // symbol any description we want and users won't normally see it. For + // debugging, use the class name. + auto privateBrandName = innerName; + if (!innerName) { + privateBrandName = nameForAnonymousClass + ? nameForAnonymousClass + : TaggedParserAtomIndex::WellKnown::anonymous(); + } + if (!emitNewPrivateNames(privateBrandName, classMembers)) { + return false; + } + } + + bool hasNameOnStack = nameKind == ClassNameKind::ComputedName; + if (isDerived) { + if (!ce.emitDerivedClass(innerName, nameForAnonymousClass, + hasNameOnStack)) { + // [stack] HOMEOBJ HERITAGE + return false; + } + } else { + if (!ce.emitClass(innerName, nameForAnonymousClass, hasNameOnStack)) { + // [stack] HOMEOBJ + return false; + } + } + + // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE + // is not used, an implicit value of %FunctionPrototype% is implied. + + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. + Maybe lse; + FunctionNode* ctor; +#ifdef ENABLE_DECORATORS + bool extraInitializersPresent = false; +#endif + if (constructor->is()) { + LexicalScopeNode* constructorScope = &constructor->as(); + + MOZ_ASSERT(!constructorScope->isEmptyScope()); +#ifdef ENABLE_DECORATORS + // With decorators enabled we expect to see |.initializers|, + // and |.instanceExtraInitializers| in this scope. + MOZ_ASSERT(constructorScope->scopeBindings()->length == 2); + MOZ_ASSERT(GetScopeDataTrailingNames(constructorScope->scopeBindings())[0] + .name() == + TaggedParserAtomIndex::WellKnown::dot_initializers_()); + MOZ_ASSERT( + GetScopeDataTrailingNames(constructorScope->scopeBindings())[1] + .name() == + TaggedParserAtomIndex::WellKnown::dot_instanceExtraInitializers_()); + + // We should only call this code if we know decorators are present, see bug + // 1871147. + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, + constructorScope->scopeBindings())) { + return false; + } + + // TODO: See bug 1868220 for support for static extra initializers. + if (!ce.prepareForExtraInitializers(TaggedParserAtomIndex::WellKnown:: + dot_instanceExtraInitializers_())) { + return false; + } + + if (classNode->addInitializerFunction()) { + DecoratorEmitter de(this); + if (!de.emitCreateAddInitializerFunction( + classNode->addInitializerFunction(), + TaggedParserAtomIndex::WellKnown:: + dot_instanceExtraInitializers_())) { + // [stack] HOMEOBJ HERITAGE? ADDINIT + return false; + } + + if (!emitUnpickN(isDerived ? 2 : 1)) { + // [stack] ADDINIT HOMEOBJ HERITAGE? + return false; + } + + extraInitializersPresent = true; + } +#else + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(constructorScope->scopeBindings()->length == 1); + MOZ_ASSERT(GetScopeDataTrailingNames(constructorScope->scopeBindings())[0] + .name() == + TaggedParserAtomIndex::WellKnown::dot_initializers_()); +#endif + + auto needsInitializer = [](ParseNode* propdef) { + return NeedsFieldInitializer(propdef, false) || + NeedsAccessorInitializer(propdef, false); + }; + + // As an optimization omit the |.initializers| binding when no instance + // fields or private methods are present. + bool needsInitializers = + std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), needsInitializer); + if (needsInitializers) { +#ifndef ENABLE_DECORATORS + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, + constructorScope->scopeBindings())) { + return false; + } +#endif + // Any class with field initializers will have a constructor + if (!emitCreateMemberInitializers(ce, classMembers, + FieldPlacement::Instance +#ifdef ENABLE_DECORATORS + , + isDerived +#endif + )) { + return false; + } + } + + ctor = &constructorScope->scopeBody()->as().method(); + } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); + ctor = &constructor->as().method(); + } + + bool needsHomeObject = ctor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (nameKind == ClassNameKind::InferredName) { + if (!setFunName(ctor->funbox(), nameForAnonymousClass)) { + return false; + } + } + if (!emitFunction(ctor, isDerived)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (lse.isSome()) { + if (!lse->emitEnd()) { + return false; + } + lse.reset(); + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; + } + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) { + return false; + } + +#ifdef ENABLE_DECORATORS + // TODO: See Bug 1868220 for support for static extra initializers. + if (!emit1(JSOp::Undefined)) { + // [stack] ADDINIT? CTOR HOMEOBJ UNDEFINED + return false; + } + if (!emitUnpickN(2)) { + // [stack] ADDINIT? UNDEFINED CTOR HOMEOBJ + } +#endif + + if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static +#ifdef ENABLE_DECORATORS + , + false +#endif + )) { + return false; + } + +#ifdef ENABLE_DECORATORS + if (!emitPickN(2)) { + // [stack] ADDINIT? CTOR HOMEOBJ UNDEFINED + return false; + } + if (!emitPopN(1)) { + // [stack] ADDINIT? CTOR HOMEOBJ + return false; + } +#endif + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) { + return false; + } + + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef ENABLE_DECORATORS + if (extraInitializersPresent) { + if (!emitPickN(2)) { + // [stack] CTOR HOMEOBJ ADDINIT + return false; + } + if (!emitPopN(1)) { + // [stack] CTOR HOMEOBJ + return false; + } + } +#endif + + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + +#if ENABLE_DECORATORS + if (!ce.prepareForDecorators()) { + // [stack] CTOR + return false; + } + if (classNode->decorators() != nullptr) { + DecoratorEmitter de(this); + NameNode* className = + classNode->names() ? classNode->names()->innerBinding() : nullptr; + if (!de.emitApplyDecoratorsToClassDefinition(className, + classNode->decorators())) { + // [stack] CTOR + return false; + } + } +#endif + + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + return false; + } + + return true; +} + +bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) { + MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt)); + + ParseNode* valueNode = exportNode->left(); + if (valueNode->isDirectRHSAnonFunction()) { + MOZ_ASSERT(exportNode->right()); + + if (!emitAnonymousFunctionWithName( + valueNode, TaggedParserAtomIndex::WellKnown::default_())) { + return false; + } + } else { + if (!emitTree(valueNode)) { + return false; + } + } + + if (ParseNode* binding = exportNode->right()) { + if (!emitLexicalInitialization(&binding->as())) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitTree( + ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, + EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + + /* 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: + MOZ_ASSERT_UNREACHABLE( + "ParamsBody should be handled in emitFunctionScript."); + break; + + case ParseNodeKind::IfStmt: + if (!emitIf(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::SwitchStmt: + if (!emitSwitch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::WhileStmt: + if (!emitWhile(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DoWhileStmt: + if (!emitDo(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ForStmt: + if (!emitFor(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::BreakStmt: + // Ensure that the column of the 'break' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitBreak(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::ContinueStmt: + // Ensure that the column of the 'continue' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitContinue(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::WithStmt: + if (!emitWith(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TryStmt: + if (!emitTry(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Catch: + if (!emitCatch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::VarStmt: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ReturnStmt: + if (!emitReturn(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::YieldStarExpr: + 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::YieldExpr: + if (!emitYield(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::AwaitExpr: + if (!emitAwaitInInnermostScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::StatementList: + if (!emitStatementList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::EmptyStmt: + break; + + case ParseNodeKind::ExpressionStmt: + if (!emitExpressionStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::LabelStmt: + if (!emitLabeledStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::CommaExpr: + if (!emitSequenceExpr(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::InitExpr: + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: { + BinaryNode* assignNode = &pn->as(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->left(), + assignNode->right())) { + return false; + } + break; + } + + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + if (!emitShortCircuitAssignment(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConditionalExpr: + if (!emitConditionalExpression(pn->as(), + valueUsage)) { + return false; + } + break; + + case ParseNodeKind::OrExpr: + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::AndExpr: + if (!emitShortCircuit(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::StrictNeExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + if (!emitLeftAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PrivateInExpr: + if (!emitPrivateInExpr(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PowExpr: + if (!emitRightAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TypeOfNameExpr: + if (!emitTypeof(&pn->as(), JSOp::Typeof)) { + return false; + } + break; + + case ParseNodeKind::TypeOfExpr: + if (!emitTypeof(&pn->as(), JSOp::TypeofExpr)) { + return false; + } + break; + + case ParseNodeKind::ThrowStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + [[fallthrough]]; + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + if (!emitUnary(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PostDecrementExpr: + if (!emitIncOrDec(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DeleteNameExpr: + if (!emitDeleteName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeletePropExpr: + if (!emitDeleteProperty(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteElemExpr: + if (!emitDeleteElement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteExpr: + if (!emitDeleteExpression(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteOptionalChainExpr: + if (!emitDeleteOptionalChain(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::OptionalChain: + if (!emitOptionalChain(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as(); + 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)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &pn->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGet()) { + // [stack] VALUE + return false; + } + + break; + } + + case ParseNodeKind::NewExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::SuperCallExpr: + if (!emitCallOrNew(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::LexicalScope: + if (!emitLexicalScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ImportDecl: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::ExportStmt: { + 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::ExportDefaultStmt: + MOZ_ASSERT(sc->isModuleContext()); + if (!emitExportDefault(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ExportFromStmt: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::CallSiteObj: + if (!emitCallSiteObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ArrayExpr: + if (!emitArrayLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ObjectExpr: + if (!emitObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitGetName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PrivateName: + if (!emitGetPrivateName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringListExpr: + if (!emitTemplateString(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::StringExpr: + if (!emitStringOp(JSOp::String, pn->as().atom())) { + return false; + } + break; + + case ParseNodeKind::NumberExpr: + if (!emitNumberOp(pn->as().value())) { + return false; + } + break; + + case ParseNodeKind::BigIntExpr: + if (!emitBigIntOp(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::RegExpExpr: { + GCThingIndex index; + if (!perScriptData().gcThingList().append(&pn->as(), + &index)) { + return false; + } + if (!emitRegExp(index)) { + return false; + } + break; + } + + case ParseNodeKind::TrueExpr: + if (!emit1(JSOp::True)) { + return false; + } + break; + case ParseNodeKind::FalseExpr: + if (!emit1(JSOp::False)) { + return false; + } + break; + case ParseNodeKind::NullExpr: + if (!emit1(JSOp::Null)) { + return false; + } + break; + case ParseNodeKind::RawUndefinedExpr: + if (!emit1(JSOp::Undefined)) { + return false; + } + break; + + case ParseNodeKind::ThisExpr: + if (!emitThisLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DebuggerStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emit1(JSOp::Debugger)) { + return false; + } + break; + + case ParseNodeKind::ClassDecl: + if (!emitClass(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::NewTargetExpr: + if (!emitNewTarget(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ImportMetaExpr: + if (!emit1(JSOp::ImportMeta)) { + return false; + } + break; + + case ParseNodeKind::CallImportExpr: { + BinaryNode* spec = &pn->as().right()->as(); + + if (!emitTree(spec->left())) { + // [stack] specifier + return false; + } + + if (!spec->right()->isKind(ParseNodeKind::PosHolder)) { + // [stack] specifier options + if (!emitTree(spec->right())) { + return false; + } + } else { + // [stack] specifier undefined + if (!emit1(JSOp::Undefined)) { + return false; + } + } + + if (!emit1(JSOp::DynamicImport)) { + return false; + } + + break; + } + + case ParseNodeKind::SetThis: + if (!emitSetThis(&pn->as())) { + return false; + } + break; + +#ifdef ENABLE_RECORD_TUPLE + case ParseNodeKind::RecordExpr: + if (!emitRecordLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TupleExpr: + if (!emitTupleLiteral(&pn->as())) { + return false; + } + break; +#endif + + case ParseNodeKind::PropertyNameExpr: + case ParseNodeKind::PosHolder: + MOZ_FALLTHROUGH_ASSERT( + "Should never try to emit ParseNodeKind::PosHolder or ::Property"); + + default: + MOZ_ASSERT(0); + } + + return true; +} + +static bool AllocSrcNote(FrontendContext* fc, SrcNotesVector& notes, + unsigned size, unsigned* index) { + size_t oldLength = notes.length(); + + if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) { + ReportAllocationOverflow(fc); + return false; + } + + if (!notes.growByUninitialized(size)) { + return false; + } + + *index = oldLength; + return true; +} + +bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end) { + MOZ_ASSERT(!inPrologue()); + return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end); +} + +bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) { + SrcNotesVector& notes = bytecodeSection().notes(); + unsigned index; + + /* + * 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. + */ + BytecodeOffset offset = bytecodeSection().offset(); + ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value(); + bytecodeSection().setLastNoteOffset(offset); + + auto allocator = [&](unsigned size) -> SrcNote* { + if (!AllocSrcNote(fc, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + if (!SrcNoteWriter::writeNote(type, delta, allocator)) { + return false; + } + + if (indexp) { + *indexp = index; + } + + if (type == SrcNoteType::NewLine || type == SrcNoteType::SetLine) { + lastLineOnlySrcNoteIndex = index; + } else { + lastLineOnlySrcNoteIndex = LastSrcNoteIsNotLineOnly; + } + + return true; +} + +bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, + unsigned* indexp) { + unsigned index; + if (!newSrcNote(type, &index)) { + return false; + } + if (!newSrcNoteOperand(offset)) { + return false; + } + if (indexp) { + *indexp = index; + } + return true; +} + +bool BytecodeEmitter::convertLastNewLineToNewLineColumn( + JS::LimitedColumnNumberOneOrigin column) { + SrcNotesVector& notes = bytecodeSection().notes(); + MOZ_ASSERT(lastLineOnlySrcNoteIndex == notes.length() - 1); + SrcNote* sn = ¬es[lastLineOnlySrcNoteIndex]; + MOZ_ASSERT(sn->type() == SrcNoteType::NewLine); + + SrcNoteWriter::convertNote(sn, SrcNoteType::NewLineColumn); + if (!newSrcNoteOperand(SrcNote::NewLineColumn::toOperand(column))) { + return false; + } + + lastLineOnlySrcNoteIndex = LastSrcNoteIsNotLineOnly; + return true; +} + +bool BytecodeEmitter::convertLastSetLineToSetLineColumn( + JS::LimitedColumnNumberOneOrigin column) { + SrcNotesVector& notes = bytecodeSection().notes(); + // The Line operand is either 1 byte or 4 bytes. + MOZ_ASSERT(lastLineOnlySrcNoteIndex == notes.length() - 1 - 1 || + lastLineOnlySrcNoteIndex == notes.length() - 1 - 4); + SrcNote* sn = ¬es[lastLineOnlySrcNoteIndex]; + MOZ_ASSERT(sn->type() == SrcNoteType::SetLine); + + SrcNoteWriter::convertNote(sn, SrcNoteType::SetLineColumn); + if (!newSrcNoteOperand(SrcNote::SetLineColumn::columnToOperand(column))) { + return false; + } + + lastLineOnlySrcNoteIndex = LastSrcNoteIsNotLineOnly; + return true; +} + +bool BytecodeEmitter::newSrcNoteOperand(ptrdiff_t operand) { + if (!SrcNote::isRepresentableOperand(operand)) { + reportError(nullptr, JSMSG_NEED_DIET, "script"); + return false; + } + + SrcNotesVector& notes = bytecodeSection().notes(); + + auto allocator = [&](unsigned size) -> SrcNote* { + unsigned index; + if (!AllocSrcNote(fc, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + return SrcNoteWriter::writeOperand(operand, allocator); +} + +bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) { + js::UniquePtr immutableScriptData = + createImmutableScriptData(); + if (!immutableScriptData) { + return false; + } + + MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() == + sc->hasNonSyntacticScope()); + + auto& things = perScriptData().gcThingList().objects(); + if (!compilationState.appendGCThings(fc, scriptIndex, things)) { + return false; + } + + // Hand over the ImmutableScriptData instance generated by BCE. + auto* sharedData = + SharedImmutableScriptData::createWith(fc, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + // De-duplicate the bytecode within the runtime. + if (!compilationState.sharedData.addAndShare(fc, scriptIndex, sharedData)) { + return false; + } + + ScriptStencil& script = compilationState.scriptData[scriptIndex]; + script.setHasSharedData(); + + // Update flags specific to functions. + if (sc->isFunctionBox()) { + FunctionBox* funbox = sc->asFunctionBox(); + MOZ_ASSERT(&script == &funbox->functionStencil()); + funbox->copyUpdatedImmutableFlags(); + MOZ_ASSERT(script.isFunction()); + } else { + ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex]; + sc->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +SelfHostedIter BytecodeEmitter::getSelfHostedIterFor(ParseNode* parseNode) { + if (emitterMode == BytecodeEmitter::SelfHosting && + parseNode->isKind(ParseNodeKind::CallExpr)) { + auto* callee = parseNode->as().callee(); + if (callee->isName(TaggedParserAtomIndex::WellKnown::allowContentIter())) { + return SelfHostedIter::AllowContent; + } + if (callee->isName( + TaggedParserAtomIndex::WellKnown::allowContentIterWith())) { + return SelfHostedIter::AllowContentWith; + } + if (callee->isName( + TaggedParserAtomIndex::WellKnown::allowContentIterWithNext())) { + return SelfHostedIter::AllowContentWithNext; + } + } + + return SelfHostedIter::Deny; +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void BytecodeEmitter::dumpAtom(TaggedParserAtomIndex index) const { + parserAtoms().dump(index); +} +#endif -- cgit v1.2.3