зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1456006 - Part 1: Add SwitchEmitter. r=jwalden
This commit is contained in:
Родитель
d9e4da676c
Коммит
d9086904d4
|
@ -29,6 +29,7 @@
|
|||
#include "frontend/ForOfLoopControl.h"
|
||||
#include "frontend/IfEmitter.h"
|
||||
#include "frontend/Parser.h"
|
||||
#include "frontend/SwitchEmitter.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "frontend/TryEmitter.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
|
@ -2341,38 +2342,27 @@ BytecodeEmitter::emitNumberOp(double dval)
|
|||
MOZ_NEVER_INLINE bool
|
||||
BytecodeEmitter::emitSwitch(ParseNode* pn)
|
||||
{
|
||||
ParseNode* cases = pn->pn_right;
|
||||
MOZ_ASSERT(cases->isKind(ParseNodeKind::LexicalScope) ||
|
||||
cases->isKind(ParseNodeKind::StatementList));
|
||||
ParseNode* lexical = pn->pn_right;
|
||||
MOZ_ASSERT(lexical->isKind(ParseNodeKind::LexicalScope));
|
||||
ParseNode* cases = lexical->scopeBody();
|
||||
MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
|
||||
|
||||
// Ensure that the column of the switch statement is set properly.
|
||||
if (!updateSourceCoordNotes(pn->pn_pos.begin))
|
||||
SwitchEmitter se(this);
|
||||
if (!se.emitDiscriminant(Some(pn->pn_pos.begin)))
|
||||
return false;
|
||||
|
||||
// Emit code for the discriminant.
|
||||
if (!emitTree(pn->pn_left))
|
||||
return false;
|
||||
|
||||
// Enter the scope before pushing the switch BreakableControl since all
|
||||
// breaks are under this scope.
|
||||
Maybe<TDZCheckCache> tdzCache;
|
||||
Maybe<EmitterScope> emitterScope;
|
||||
if (cases->isKind(ParseNodeKind::LexicalScope)) {
|
||||
if (!cases->isEmptyScope()) {
|
||||
tdzCache.emplace(this);
|
||||
emitterScope.emplace(this);
|
||||
if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advance |cases| to refer to the switch case list.
|
||||
cases = cases->scopeBody();
|
||||
if (!lexical->isEmptyScope()) {
|
||||
if (!se.emitLexical(lexical->scopeBindings()))
|
||||
return false;
|
||||
|
||||
// A switch statement may contain hoisted functions inside its
|
||||
// cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
|
||||
// bodies of the cases to the case list.
|
||||
if (cases->pn_xflags & PNX_FUNCDEFS) {
|
||||
MOZ_ASSERT(emitterScope);
|
||||
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
|
||||
if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
|
||||
if (!emitHoistedFunctionsInList(caseNode->pn_right))
|
||||
|
@ -2380,305 +2370,104 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After entering the scope, push the switch control.
|
||||
BreakableControl controlInfo(this, StatementKind::Switch);
|
||||
|
||||
ptrdiff_t top = offset();
|
||||
|
||||
// Switch bytecodes run from here till end of final case.
|
||||
uint32_t caseCount = cases->pn_count;
|
||||
if (caseCount > JS_BIT(16)) {
|
||||
reportError(pn, JSMSG_TOO_MANY_CASES);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try for most optimal, fall back if not dense ints.
|
||||
JSOp switchOp = JSOP_TABLESWITCH;
|
||||
uint32_t tableLength = 0;
|
||||
int32_t low, high;
|
||||
bool hasDefault = false;
|
||||
CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
|
||||
if (caseCount == 0 ||
|
||||
(caseCount == 1 && (hasDefault = firstCase->isDefault())))
|
||||
{
|
||||
low = 0;
|
||||
high = -1;
|
||||
} else {
|
||||
Vector<size_t, 128, SystemAllocPolicy> intmap;
|
||||
int32_t intmapBitLength = 0;
|
||||
|
||||
low = JSVAL_INT_MAX;
|
||||
high = JSVAL_INT_MIN;
|
||||
MOZ_ASSERT(!(cases->pn_xflags & PNX_FUNCDEFS));
|
||||
}
|
||||
|
||||
SwitchEmitter::TableGenerator tableGen(this);
|
||||
uint32_t caseCount = cases->pn_count;
|
||||
CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
|
||||
if (caseCount == 0) {
|
||||
tableGen.finish(0);
|
||||
} else if (caseCount == 1 && firstCase->isDefault()) {
|
||||
caseCount = 0;
|
||||
tableGen.finish(0);
|
||||
} else {
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (caseNode->isDefault()) {
|
||||
hasDefault = true;
|
||||
caseCount--; // one of the "cases" was the default
|
||||
continue;
|
||||
}
|
||||
|
||||
if (switchOp == JSOP_CONDSWITCH)
|
||||
if (tableGen.isInvalid())
|
||||
continue;
|
||||
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
|
||||
if (caseValue->getKind() != ParseNodeKind::Number) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
tableGen.setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t i;
|
||||
if (!NumberEqualsInt32(caseValue->pn_dval, &i)) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
tableGen.setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
continue;
|
||||
}
|
||||
if (i < low)
|
||||
low = i;
|
||||
if (i > high)
|
||||
high = i;
|
||||
|
||||
// Check for duplicates, which require a JSOP_CONDSWITCH.
|
||||
// We bias i by 65536 if it's negative, and hope that's a rare
|
||||
// case (because it requires a malloc'd bitmap).
|
||||
if (i < 0)
|
||||
i += JS_BIT(16);
|
||||
if (i >= intmapBitLength) {
|
||||
size_t newLength = NumWordsForBitArrayOfLength(i + 1);
|
||||
if (!intmap.resize(newLength)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
intmapBitLength = newLength * BitArrayElementBits;
|
||||
}
|
||||
if (IsBitArrayElementSet(intmap.begin(), intmap.length(), i)) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
continue;
|
||||
}
|
||||
SetBitArrayElement(intmap.begin(), intmap.length(), i);
|
||||
if (!tableGen.addNumber(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute table length and select condswitch instead if overlarge or
|
||||
// more than half-sparse.
|
||||
if (switchOp == JSOP_TABLESWITCH) {
|
||||
tableLength = uint32_t(high - low + 1);
|
||||
if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount)
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
}
|
||||
tableGen.finish(caseCount);
|
||||
}
|
||||
|
||||
// The note has one or two offsets: first tells total switch code length;
|
||||
// second (if condswitch) tells offset to first JSOP_CASE.
|
||||
unsigned noteIndex;
|
||||
size_t switchSize;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
// 0 bytes of immediate for unoptimized switch.
|
||||
switchSize = 0;
|
||||
if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex))
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
// 3 offsets (len, low, high) before the table, 1 per entry.
|
||||
switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength));
|
||||
if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Emit switchOp followed by switchSize bytes of jump or lookup table.
|
||||
MOZ_ASSERT(top == offset());
|
||||
if (!emitN(switchOp, switchSize))
|
||||
if (!se.validateCaseCount(caseCount))
|
||||
return false;
|
||||
|
||||
Vector<CaseClause*, 32, SystemAllocPolicy> table;
|
||||
|
||||
JumpList condSwitchDefaultOff;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
unsigned caseNoteIndex;
|
||||
bool beforeCases = true;
|
||||
ptrdiff_t lastCaseOffset = -1;
|
||||
|
||||
// The case conditions need their own TDZ cache since they might not
|
||||
// all execute.
|
||||
TDZCheckCache tdzCache(this);
|
||||
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 (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
if (caseNode->isDefault())
|
||||
continue;
|
||||
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
// If the expression is a literal, suppress line number emission so
|
||||
// that debugging works more naturally.
|
||||
if (caseValue) {
|
||||
if (!emitTree(caseValue, ValueUsage::WantValue,
|
||||
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!beforeCases) {
|
||||
// prevCase is the previous JSOP_CASE's bytecode offset.
|
||||
if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
|
||||
return false;
|
||||
}
|
||||
if (!caseValue) {
|
||||
// This is the default clause.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex))
|
||||
return false;
|
||||
|
||||
// The case clauses are produced before any of the case body. The
|
||||
// JumpList is saved on the parsed tree, then later restored and
|
||||
// patched when generating the cases body.
|
||||
JumpList caseJump;
|
||||
if (!emitJump(JSOP_CASE, &caseJump))
|
||||
return false;
|
||||
caseNode->setOffset(caseJump.offset);
|
||||
lastCaseOffset = caseJump.offset;
|
||||
|
||||
if (beforeCases) {
|
||||
// Switch note's second offset is to first JSOP_CASE.
|
||||
unsigned noteCount = notes().length();
|
||||
if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top))
|
||||
return false;
|
||||
unsigned noteCountDelta = notes().length() - noteCount;
|
||||
if (noteCountDelta != 0)
|
||||
caseNoteIndex += noteCountDelta;
|
||||
beforeCases = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't have an explicit default (which could fall in between
|
||||
// cases, preventing us from fusing this setSrcNoteOffset with the call
|
||||
// in the loop above), link the last case to the implicit default for
|
||||
// the benefit of IonBuilder.
|
||||
if (!hasDefault &&
|
||||
!beforeCases &&
|
||||
!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Emit default even if no explicit default statement.
|
||||
if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff))
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
// skip default offset.
|
||||
jsbytecode* pc = code(top + JUMP_OFFSET_LEN);
|
||||
|
||||
// Fill in switch bounds, which we know fit in 16-bit offsets.
|
||||
SET_JUMP_OFFSET(pc, low);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
SET_JUMP_OFFSET(pc, high);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
|
||||
if (tableLength != 0) {
|
||||
if (!table.growBy(tableLength)) {
|
||||
ReportOutOfMemory(cx);
|
||||
if (!emitTree(caseValue, ValueUsage::WantValue,
|
||||
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (ParseNode* caseValue = caseNode->caseExpression()) {
|
||||
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
|
||||
|
||||
int32_t i = int32_t(caseValue->pn_dval);
|
||||
MOZ_ASSERT(double(i) == caseValue->pn_dval);
|
||||
|
||||
i -= low;
|
||||
MOZ_ASSERT(uint32_t(i) < tableLength);
|
||||
MOZ_ASSERT(!table[i]);
|
||||
table[i] = caseNode;
|
||||
}
|
||||
}
|
||||
if (!se.emitCaseJump())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JumpTarget defaultOffset{ -1 };
|
||||
|
||||
// Emit code for each case's statements.
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) {
|
||||
// The case offset got saved in the caseNode structure after
|
||||
// emitting the JSOP_CASE jump instruction above.
|
||||
JumpList caseCond;
|
||||
caseCond.offset = caseNode->offset();
|
||||
if (!emitJumpTargetAndPatch(caseCond))
|
||||
if (caseNode->isDefault()) {
|
||||
if (!se.emitDefaultBody())
|
||||
return false;
|
||||
} else {
|
||||
if (isTableSwitch) {
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
|
||||
|
||||
int32_t i = int32_t(caseValue->pn_dval);
|
||||
MOZ_ASSERT(double(i) == caseValue->pn_dval);
|
||||
|
||||
if (!se.emitCaseBody(i, tableGen))
|
||||
return false;
|
||||
} else {
|
||||
if (!se.emitCaseBody())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JumpTarget here;
|
||||
if (!emitJumpTarget(&here))
|
||||
return false;
|
||||
if (caseNode->isDefault())
|
||||
defaultOffset = here;
|
||||
|
||||
// If this is emitted as a TABLESWITCH, we'll need to know this case's
|
||||
// offset later when emitting the table. Store it in the node's
|
||||
// pn_offset (giving the field a different meaning vs. how we used it
|
||||
// on the immediately preceding line of code).
|
||||
caseNode->setOffset(here.offset);
|
||||
|
||||
TDZCheckCache tdzCache(this);
|
||||
|
||||
if (!emitTree(caseNode->statementList()))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasDefault) {
|
||||
// If no default case, offset for default is to end of switch.
|
||||
if (!emitJumpTarget(&defaultOffset))
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(defaultOffset.offset != -1);
|
||||
|
||||
// Set the default offset (to end of switch if no default).
|
||||
jsbytecode* pc;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
pc = nullptr;
|
||||
patchJumpsToTarget(condSwitchDefaultOff, defaultOffset);
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
pc = code(top);
|
||||
SET_JUMP_OFFSET(pc, defaultOffset.offset - top);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
|
||||
// Set the SRC_SWITCH note's offset operand to tell end of switch.
|
||||
if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top))
|
||||
return false;
|
||||
|
||||
if (switchOp == JSOP_TABLESWITCH) {
|
||||
// Skip over the already-initialized switch bounds.
|
||||
pc += 2 * JUMP_OFFSET_LEN;
|
||||
|
||||
// Fill in the jump table, if there is one.
|
||||
for (uint32_t i = 0; i < tableLength; i++) {
|
||||
CaseClause* caseNode = table[i];
|
||||
ptrdiff_t off = caseNode ? caseNode->offset() - top : 0;
|
||||
SET_JUMP_OFFSET(pc, off);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Patch breaks before leaving the scope, as all breaks are under the
|
||||
// lexical scope if it exists.
|
||||
if (!controlInfo.patchBreaks(this))
|
||||
return false;
|
||||
|
||||
if (emitterScope && !emitterScope->leave(this))
|
||||
if (!se.emitEnd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -255,16 +255,13 @@ IsTypeofKind(ParseNodeKind kind)
|
|||
* StatementList list pn_head: list of pn_count statements
|
||||
* If ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null.
|
||||
* Switch binary pn_left: discriminant
|
||||
* pn_right: list of Case nodes, with at most one
|
||||
* default node, or if there are let bindings
|
||||
* in the top level of the switch body's cases, a
|
||||
* LexicalScope node that contains the list of
|
||||
* Case nodes.
|
||||
* pn_right: LexicalScope node that contains the list
|
||||
* of Case nodes, with at most one
|
||||
* default node.
|
||||
* Case binary pn_left: case-expression if CaseClause, or
|
||||
* null if DefaultClause
|
||||
* pn_right: StatementList node for this case's
|
||||
* statements
|
||||
* pn_u.binary.offset: scratch space for the emitter
|
||||
* While binary pn_left: cond, pn_right: body
|
||||
* DoWhile binary pn_left: body, pn_right: cond
|
||||
* For binary pn_left: either ForIn (for-in statement),
|
||||
|
@ -558,7 +555,6 @@ class ParseNode
|
|||
union {
|
||||
unsigned iflags; /* JSITER_* flags for ParseNodeKind::For node */
|
||||
bool isStatic; /* only for ParseNodeKind::ClassMethod */
|
||||
uint32_t offset; /* for the emitter's use on ParseNodeKind::Case nodes */
|
||||
};
|
||||
} binary;
|
||||
struct { /* one kid if unary */
|
||||
|
@ -1022,10 +1018,6 @@ class CaseClause : public BinaryNode
|
|||
// The next CaseClause in the same switch statement.
|
||||
CaseClause* next() const { return pn_next ? &pn_next->as<CaseClause>() : nullptr; }
|
||||
|
||||
// Scratch space used by the emitter.
|
||||
uint32_t offset() const { return pn_u.binary.offset; }
|
||||
void setOffset(uint32_t u) { pn_u.binary.offset = u; }
|
||||
|
||||
static bool test(const ParseNode& node) {
|
||||
bool match = node.isKind(ParseNodeKind::Case);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "frontend/SwitchEmitter.h"
|
||||
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
bool
|
||||
SwitchEmitter::TableGenerator::addNumber(int32_t caseValue)
|
||||
{
|
||||
if (isInvalid())
|
||||
return true;
|
||||
|
||||
if (unsigned(caseValue + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (intmap_.isNothing())
|
||||
intmap_.emplace();
|
||||
|
||||
low_ = std::min(low_, caseValue);
|
||||
high_ = std::max(high_, caseValue);
|
||||
|
||||
// Check for duplicates, which require a JSOP_CONDSWITCH.
|
||||
// We bias caseValue by 65536 if it's negative, and hope that's a rare case
|
||||
// (because it requires a malloc'd bitmap).
|
||||
if (caseValue < 0)
|
||||
caseValue += JS_BIT(16);
|
||||
if (caseValue >= intmapBitLength_) {
|
||||
size_t newLength = NumWordsForBitArrayOfLength(caseValue + 1);
|
||||
if (!intmap_->resize(newLength)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
intmapBitLength_ = newLength * BitArrayElementBits;
|
||||
}
|
||||
if (IsBitArrayElementSet(intmap_->begin(), intmap_->length(), caseValue)) {
|
||||
// Duplicate entry is not supported in table switch.
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
SetBitArrayElement(intmap_->begin(), intmap_->length(), caseValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SwitchEmitter::TableGenerator::finish(uint32_t caseCount)
|
||||
{
|
||||
intmap_.reset();
|
||||
|
||||
#ifdef DEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
|
||||
if (isInvalid())
|
||||
return;
|
||||
|
||||
if (caseCount == 0) {
|
||||
low_ = 0;
|
||||
high_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute table length and select condswitch instead if overlarge
|
||||
// or more than half-sparse.
|
||||
tableLength_ = uint32_t(high_ - low_ + 1);
|
||||
if (tableLength_ >= JS_BIT(16) || tableLength_ > 2 * caseCount)
|
||||
setInvalid();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::toCaseIndex(int32_t caseValue) const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
uint32_t caseIndex = uint32_t(caseValue - low_);
|
||||
MOZ_ASSERT(caseIndex < tableLength_);
|
||||
return caseIndex;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::tableLength() const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
return tableLength_;
|
||||
}
|
||||
|
||||
SwitchEmitter::SwitchEmitter(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDiscriminant(const Maybe<uint32_t>& switchPos)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
switchPos_ = switchPos;
|
||||
|
||||
if (switchPos_) {
|
||||
// Ensure that the column of the switch statement is set properly.
|
||||
if (!bce_->updateSourceCoordNotes(*switchPos_))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = State::Discriminant;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitLexical(Handle<LexicalScope::Data*> bindings)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant);
|
||||
MOZ_ASSERT(bindings);
|
||||
|
||||
tdzCacheLexical_.emplace(bce_);
|
||||
emitterScope_.emplace(bce_);
|
||||
if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings))
|
||||
return false;
|
||||
|
||||
state_ = State::Lexical;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::validateCaseCount(uint32_t caseCount)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant || state_ == State::Lexical);
|
||||
if (caseCount > JS_BIT(16)) {
|
||||
bce_->reportError(switchPos_, JSMSG_TOO_MANY_CASES);
|
||||
return false;
|
||||
}
|
||||
caseCount_ = caseCount;
|
||||
|
||||
state_ = State::CaseCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCond()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
|
||||
kind_ = Kind::Cond;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
if (!caseOffsets_.resize(caseCount_)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The note has two offsets: first tells total switch code length;
|
||||
// second tells offset to first JSOP_CASE.
|
||||
if (!bce_->newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_CONDSWITCH, 0))
|
||||
return false;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::Cond;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitTable(const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
kind_ = Kind::Table;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
// The note has one offset that tells total switch code length.
|
||||
|
||||
// 3 offsets (len, low, high) before the table, 1 per entry.
|
||||
size_t switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableGen.tableLength()));
|
||||
if (!bce_->newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
if (!caseOffsets_.resize(tableGen.tableLength())) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_TABLESWITCH, switchSize))
|
||||
return false;
|
||||
|
||||
// Skip default offset.
|
||||
jsbytecode* pc = bce_->code(top_ + JUMP_OFFSET_LEN);
|
||||
|
||||
// Fill in switch bounds, which we know fit in 16-bit offsets.
|
||||
SET_JUMP_OFFSET(pc, tableGen.low());
|
||||
SET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN, tableGen.high());
|
||||
|
||||
state_ = State::Table;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
|
||||
if (state_ == State::Case) {
|
||||
// Link the last JSOP_CASE's SRC_NEXTCASE to current JSOP_CASE or
|
||||
// JSOP_DEFAULT for the benefit of IonBuilder.
|
||||
if (!bce_->setSrcNoteOffset(caseNoteIndex_, 0, bce_->offset() - lastCaseOffset_))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDefault) {
|
||||
if (!bce_->emitJump(JSOP_DEFAULT, &condSwitchDefaultOffset_))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bce_->newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex_))
|
||||
return false;
|
||||
|
||||
JumpList caseJump;
|
||||
if (!bce_->emitJump(JSOP_CASE, &caseJump))
|
||||
return false;
|
||||
caseOffsets_[caseIndex] = caseJump.offset;
|
||||
lastCaseOffset_ = caseJump.offset;
|
||||
|
||||
if (state_ == State::Cond) {
|
||||
// Switch note's second offset is to first JSOP_CASE.
|
||||
unsigned noteCount = bce_->notes().length();
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 1, lastCaseOffset_ - top_))
|
||||
return false;
|
||||
unsigned noteCountDelta = bce_->notes().length() - noteCount;
|
||||
if (noteCountDelta != 0)
|
||||
caseNoteIndex_ += noteCountDelta;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseJump()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(caseIndex_, false))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
state_ = State::Case;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitImplicitDefault()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(0, true))
|
||||
return false;
|
||||
|
||||
caseIndex_ = 0;
|
||||
|
||||
// No internal state after emitting default jump.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
|
||||
JumpList caseJump;
|
||||
caseJump.offset = caseOffsets_[caseIndex_];
|
||||
if (!bce_->emitJumpTargetAndPatch(caseJump))
|
||||
return false;
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody(int32_t caseValue, const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Table);
|
||||
MOZ_ASSERT(state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseOffsets_[tableGen.toCaseIndex(caseValue)] = here.offset;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDefaultBody()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::Case ||
|
||||
state_ == State::CaseBody);
|
||||
MOZ_ASSERT(!hasDefault_);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
defaultJumpTargetOffset_ = here;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
hasDefault_ = true;
|
||||
state_ = State::DefaultBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (!hasDefault_) {
|
||||
// If no default case, offset for default is to end of switch.
|
||||
if (!bce_->emitJumpTarget(&defaultJumpTargetOffset_))
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(defaultJumpTargetOffset_.offset != -1);
|
||||
|
||||
// Set the default offset (to end of switch if no default).
|
||||
jsbytecode* pc;
|
||||
if (kind_ == Kind::Cond) {
|
||||
pc = nullptr;
|
||||
bce_->patchJumpsToTarget(condSwitchDefaultOffset_, defaultJumpTargetOffset_);
|
||||
} else {
|
||||
// Fill in the default jump target.
|
||||
pc = bce_->code(top_);
|
||||
SET_JUMP_OFFSET(pc, defaultJumpTargetOffset_.offset - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
|
||||
// Set the SRC_SWITCH note's offset operand to tell end of switch.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->lastNonJumpTargetOffset() - top_))
|
||||
return false;
|
||||
|
||||
if (kind_ == Kind::Table) {
|
||||
// Skip over the already-initialized switch bounds.
|
||||
pc += 2 * JUMP_OFFSET_LEN;
|
||||
|
||||
// Fill in the jump table, if there is one.
|
||||
for (uint32_t i = 0, length = caseOffsets_.length(); i < length; i++) {
|
||||
ptrdiff_t off = caseOffsets_[i];
|
||||
SET_JUMP_OFFSET(pc, off == 0 ? 0 : off - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Patch breaks before leaving the scope, as all breaks are under the
|
||||
// lexical scope if it exists.
|
||||
if (!controlInfo_->patchBreaks(bce_))
|
||||
return false;
|
||||
|
||||
if (emitterScope_ && !emitterScope_->leave(bce_))
|
||||
return false;
|
||||
|
||||
emitterScope_.reset();
|
||||
tdzCacheLexical_.reset();
|
||||
|
||||
controlInfo_.reset();
|
||||
|
||||
state_ = State::End;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef frontend_SwitchEmitter_h
|
||||
#define frontend_SwitchEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/BytecodeControlStructures.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "js/AllocPolicy.h"
|
||||
#include "js/Value.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/Scope.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for switch-case-default block.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// emit(c2_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(this);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(bce);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains lexical bindings
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains hosted functions
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
// emit(hosted functions);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
class MOZ_STACK_CLASS SwitchEmitter
|
||||
{
|
||||
// Bytecode for each case.
|
||||
//
|
||||
// Cond Switch
|
||||
// {discriminant}
|
||||
// JSOP_CONDSWITCH
|
||||
//
|
||||
// {c1_expr}
|
||||
// JSOP_CASE c1
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_expr}
|
||||
// JSOP_CASE c2
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// JSOP_DEFAULT default
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// default:
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
//
|
||||
// Table Switch
|
||||
// {discriminant}
|
||||
// JSOP_TABLESWITCH c1, c2, ...
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
|
||||
public:
|
||||
enum class Kind {
|
||||
Table,
|
||||
Cond
|
||||
};
|
||||
|
||||
// Class for generating optimized table switch data.
|
||||
class MOZ_STACK_CLASS TableGenerator
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// Bit array for given numbers.
|
||||
mozilla::Maybe<js::Vector<size_t, 128, SystemAllocPolicy>> intmap_;
|
||||
|
||||
// The length of the intmap_.
|
||||
int32_t intmapBitLength_ = 0;
|
||||
|
||||
// The length of the table.
|
||||
uint32_t tableLength_ = 0;
|
||||
|
||||
// The lower and higher bounds of the table.
|
||||
int32_t low_ = JSVAL_INT_MAX, high_ = JSVAL_INT_MIN;
|
||||
|
||||
// Whether the table is still valid.
|
||||
bool valid_= true;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit TableGenerator(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
void setInvalid() {
|
||||
valid_ = false;
|
||||
}
|
||||
MOZ_MUST_USE bool isValid() const {
|
||||
return valid_;
|
||||
}
|
||||
MOZ_MUST_USE bool isInvalid() const {
|
||||
return !valid_;
|
||||
}
|
||||
|
||||
// Add the given number to the table. The number is the value of
|
||||
// `expr` for `case expr:` syntax.
|
||||
MOZ_MUST_USE bool addNumber(int32_t caseValue);
|
||||
|
||||
// Finish generating the table.
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
void finish(uint32_t caseCount);
|
||||
|
||||
private:
|
||||
friend SwitchEmitter;
|
||||
|
||||
// The following methods can be used only after calling `finish`.
|
||||
|
||||
// Returns the lower bound of the added numbers.
|
||||
int32_t low() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return low_;
|
||||
}
|
||||
|
||||
// Returns the higher bound of the numbers.
|
||||
int32_t high() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return high_;
|
||||
}
|
||||
|
||||
// Returns the index in SwitchEmitter.caseOffsets_ for table switch.
|
||||
uint32_t toCaseIndex(int32_t caseValue) const;
|
||||
|
||||
// Returns the length of the table.
|
||||
// This method can be called only if `isValid()` is true.
|
||||
uint32_t tableLength() const;
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// `kind_` should be set to the correct value in emitCond/emitTable.
|
||||
Kind kind_ = Kind::Cond;
|
||||
|
||||
// True if there's explicit default case.
|
||||
bool hasDefault_ = false;
|
||||
|
||||
// The source note index for SRC_CONDSWITCH.
|
||||
unsigned noteIndex_ = 0;
|
||||
|
||||
// Source note index of the previous SRC_NEXTCASE.
|
||||
unsigned caseNoteIndex_ = 0;
|
||||
|
||||
// The number of cases in the switch statement, excluding the default case.
|
||||
uint32_t caseCount_ = 0;
|
||||
|
||||
// Internal index for case jump and case body, used by cond switch.
|
||||
uint32_t caseIndex_ = 0;
|
||||
|
||||
// Bytecode offset after emitting `discriminant`.
|
||||
ptrdiff_t top_ = 0;
|
||||
|
||||
// Bytecode offset of the previous JSOP_CASE.
|
||||
ptrdiff_t lastCaseOffset_ = 0;
|
||||
|
||||
// Bytecode offset of the JSOP_JUMPTARGET for default body.
|
||||
JumpTarget defaultJumpTargetOffset_ = { -1 };
|
||||
|
||||
// Bytecode offset of the JSOP_DEFAULT.
|
||||
JumpList condSwitchDefaultOffset_;
|
||||
|
||||
// Instantiated when there's lexical scope for entire switch.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheLexical_;
|
||||
mozilla::Maybe<EmitterScope> emitterScope_;
|
||||
|
||||
// Instantiated while emitting case expression and case/default body.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheCaseAndBody_;
|
||||
|
||||
// Control for switch.
|
||||
mozilla::Maybe<BreakableControl> controlInfo_;
|
||||
|
||||
mozilla::Maybe<uint32_t> switchPos_;
|
||||
|
||||
// Cond Switch:
|
||||
// Offset of each JSOP_CASE.
|
||||
// Table Switch:
|
||||
// Offset of each JSOP_JUMPTARGET for case.
|
||||
js::Vector<ptrdiff_t, 32, SystemAllocPolicy> caseOffsets_;
|
||||
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ emitDiscriminant +--------------+
|
||||
// | Start |----------------->| Discriminant |-+
|
||||
// +-------+ +--------------+ |
|
||||
// |
|
||||
// +-------------------------------------------+
|
||||
// |
|
||||
// | validateCaseCount +-----------+
|
||||
// +->+------------------------>+------------------>| CaseCount |-+
|
||||
// | ^ +-----------+ |
|
||||
// | emitLexical +---------+ | |
|
||||
// +------------>| Lexical |-+ |
|
||||
// +---------+ |
|
||||
// |
|
||||
// +--------------------------------------------------------------+
|
||||
// |
|
||||
// | emitTable +-------+
|
||||
// +---------->| Table |---------------------------->+-+
|
||||
// | +-------+ ^ |
|
||||
// | | |
|
||||
// | emitCond +------+ | |
|
||||
// +---------->| Cond |-+------------------------>+->+ |
|
||||
// +------+ | ^ |
|
||||
// | | |
|
||||
// | emitCase +------+ | |
|
||||
// +->+--------->| Case |->+-+ |
|
||||
// ^ +------+ | |
|
||||
// | | |
|
||||
// +--------------------+ |
|
||||
// |
|
||||
// +---------------------------------------------------+
|
||||
// |
|
||||
// | emitEnd +-----+
|
||||
// +-+----------------------------------------->+-------->| End |
|
||||
// | ^ +-----+
|
||||
// | emitCaseBody +----------+ |
|
||||
// +->+-+---------------->| CaseBody |--->+-+-+
|
||||
// ^ | +----------+ ^ |
|
||||
// | | | |
|
||||
// | | emitDefaultBody +-------------+ | |
|
||||
// | +---------------->| DefaultBody |-+ |
|
||||
// | +-------------+ |
|
||||
// | |
|
||||
// +-------------------------------------+
|
||||
//
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitDiscriminant.
|
||||
Discriminant,
|
||||
|
||||
// After calling validateCaseCount.
|
||||
CaseCount,
|
||||
|
||||
// After calling emitLexical.
|
||||
Lexical,
|
||||
|
||||
// After calling emitCond.
|
||||
Cond,
|
||||
|
||||
// After calling emitTable.
|
||||
Table,
|
||||
|
||||
// After calling emitCase.
|
||||
Case,
|
||||
|
||||
// After calling emitCaseBody.
|
||||
CaseBody,
|
||||
|
||||
// After calling emitDefaultBody.
|
||||
DefaultBody,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_ = State::Start;
|
||||
|
||||
public:
|
||||
explicit SwitchEmitter(BytecodeEmitter* bce);
|
||||
|
||||
// `switchPos` is the offset in the source code for the character below:
|
||||
//
|
||||
// switch ( cond ) { ... }
|
||||
// ^
|
||||
// |
|
||||
// switchPos
|
||||
//
|
||||
// Can be Nothing() if not available.
|
||||
MOZ_MUST_USE bool emitDiscriminant(const mozilla::Maybe<uint32_t>& switchPos);
|
||||
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
MOZ_MUST_USE bool validateCaseCount(uint32_t caseCount);
|
||||
|
||||
// `bindings` is a lexical scope for the entire switch, in case there's
|
||||
// let/const effectively directly under case or default blocks.
|
||||
MOZ_MUST_USE bool emitLexical(Handle<LexicalScope::Data*> bindings);
|
||||
|
||||
MOZ_MUST_USE bool emitCond();
|
||||
MOZ_MUST_USE bool emitTable(const TableGenerator& tableGen);
|
||||
|
||||
MOZ_MUST_USE bool emitCaseJump();
|
||||
|
||||
MOZ_MUST_USE bool emitCaseBody();
|
||||
MOZ_MUST_USE bool emitCaseBody(int32_t caseValue, const TableGenerator& tableGen);
|
||||
MOZ_MUST_USE bool emitDefaultBody();
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault);
|
||||
MOZ_MUST_USE bool emitImplicitDefault();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_SwitchEmitter_h */
|
|
@ -219,6 +219,7 @@ UNIFIED_SOURCES += [
|
|||
'frontend/JumpList.cpp',
|
||||
'frontend/NameFunctions.cpp',
|
||||
'frontend/ParseNode.cpp',
|
||||
'frontend/SwitchEmitter.cpp',
|
||||
'frontend/TDZCheckCache.cpp',
|
||||
'frontend/TokenStream.cpp',
|
||||
'frontend/TryEmitter.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче