Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/Saturate.h"
#include "mozilla/Variant.h" // mozilla::AsVariant
#include <algorithm>
#include <iterator>
#include <string.h>
#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/UsingEmitter.h" // UsingEmitter
#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/StringBuilder.h" // StringBuilder
#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
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
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::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<StaticClassBlock>() && inStaticContext) ||
(member->is<ClassField>() &&
member->as<ClassField>().isStatic() == inStaticContext);
}
static bool NeedsAccessorInitializer(ParseNode* member, bool isStatic) {
if (isStatic) {
return false;
}
return member->is<ClassMethod>() &&
member->as<ClassMethod>().name().isKind(ParseNodeKind::PrivateName) &&
!member->as<ClassMethod>().isStatic() &&
member->as<ClassMethod>().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 <typename T>
T* BytecodeEmitter::findInnermostNestableControl() const {
return NestableControl::findNearest<T>(innermostNestableControl);
}
template <typename T, typename Predicate /* (T*) -> bool */>
T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const {
return NestableControl::findNearest<T>(innermostNestableControl, predicate);
}
NameLocation BytecodeEmitter::lookupName(TaggedParserAtomIndex name) {
return innermostEmitterScope()->lookup(this, name);
}
void BytecodeEmitter::lookupPrivate(TaggedParserAtomIndex name,
NameLocation& loc,
Maybe<NameLocation>& brandLoc) {
innermostEmitterScope()->lookupPrivate(this, name, loc, brandLoc);
}
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
TaggedParserAtomIndex name, EmitterScope* target) {
return innermostEmitterScope()->locationBoundInScope(name, target);
}
template <typename T>
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScopeType(
TaggedParserAtomIndex name, EmitterScope* source) {
EmitterScope* aScope = source;
while (!aScope->scope(this).is<T>()) {
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<uint32_t>& 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<bool> 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<LexicalScopeNode>()) {
nextpn = nextpn->as<LexicalScopeNode>().scopeBody();
}
if (nextpn->isKind(ParseNodeKind::StatementList)) {
if (ParseNode* firstStatement = nextpn->as<ListNode>().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<NullaryNode>());
*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<NameNode>());
*answer = false;
return true;
case ParseNodeKind::RegExpExpr:
MOZ_ASSERT(pn->is<RegExpLiteral>());
*answer = false;
return true;
case ParseNodeKind::NumberExpr:
MOZ_ASSERT(pn->is<NumericLiteral>());
*answer = false;
return true;
case ParseNodeKind::BigIntExpr:
MOZ_ASSERT(pn->is<BigIntLiteral>());
*answer = false;
return true;
// |this| can throw in derived class constructors, including nested arrow
// functions or eval.
case ParseNodeKind::ThisExpr:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = sc->needsThisTDZChecks();
return true;
// |new.target| doesn't have any side-effects.
case ParseNodeKind::NewTargetExpr: {
MOZ_ASSERT(pn->is<NewTargetNode>());
*answer = false;
return true;
}
// Trivial binary nodes with more token pos holders.
case ParseNodeKind::ImportMetaExpr: {
MOZ_ASSERT(pn->as<BinaryNode>().left()->isKind(ParseNodeKind::PosHolder));
MOZ_ASSERT(
pn->as<BinaryNode>().right()->isKind(ParseNodeKind::PosHolder));
*answer = false;
return true;
}
case ParseNodeKind::BreakStmt:
MOZ_ASSERT(pn->is<BreakStatement>());
*answer = true;
return true;
case ParseNodeKind::ContinueStmt:
MOZ_ASSERT(pn->is<ContinueStatement>());
*answer = true;
return true;
case ParseNodeKind::DebuggerStmt:
MOZ_ASSERT(pn->is<DebuggerStatement>());
*answer = true;
return true;
// Watch out for getters!
case ParseNodeKind::OptionalDotExpr:
case ParseNodeKind::DotExpr:
case ParseNodeKind::ArgumentsLength:
MOZ_ASSERT(pn->is<BinaryNode>());
*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<UnaryNode>().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<UnaryNode>());
*answer = true;
return true;
// Looking up or evaluating the associated name could throw.
case ParseNodeKind::TypeOfNameExpr: