[wasm] [interpreter] Precompute side table for breaks
Instead of dynamically tracking the block nesting, precompute the information statically. The interpreter was already using a side table to store the pc diff for each break, conditional break and others. The information needed to adjust the stack was tracked dynamically, however. This CL also precomputes this information, as it is statically known. Instead of just storing the pc diff in the side table, we now store the pc diff, the stack height diff and the arity of the target block. Local measurements show speedups of 5-6% on average, sometimes >10%. R=ahaas@chromium.org BUG=v8:5822 Change-Id: I986cfa989aabe1488f2ff79ddbfbb28aeffe1452 Reviewed-on: https://chromium-review.googlesource.com/485482 Reviewed-by: Andreas Haas <ahaas@chromium.org> Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Cr-Commit-Position: refs/heads/master@{#44837}
This commit is contained in:
parent
c2abd807e4
commit
92bf832799
@ -515,6 +515,68 @@ class WasmDecoder : public Decoder {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> StackEffect(const byte* pc) {
|
||||
WasmOpcode opcode = static_cast<WasmOpcode>(*pc);
|
||||
// Handle "simple" opcodes with a fixed signature first.
|
||||
FunctionSig* sig = WasmOpcodes::Signature(opcode);
|
||||
if (!sig) sig = WasmOpcodes::AsmjsSignature(opcode);
|
||||
if (sig) return {sig->parameter_count(), sig->return_count()};
|
||||
|
||||
#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name:
|
||||
// clang-format off
|
||||
switch (opcode) {
|
||||
case kExprSelect:
|
||||
return {3, 1};
|
||||
FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE)
|
||||
return {2, 0};
|
||||
FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE)
|
||||
case kExprTeeLocal:
|
||||
case kExprGrowMemory:
|
||||
return {1, 1};
|
||||
case kExprSetLocal:
|
||||
case kExprSetGlobal:
|
||||
case kExprDrop:
|
||||
case kExprBrIf:
|
||||
case kExprBrTable:
|
||||
case kExprIf:
|
||||
return {1, 0};
|
||||
case kExprGetLocal:
|
||||
case kExprGetGlobal:
|
||||
case kExprI32Const:
|
||||
case kExprI64Const:
|
||||
case kExprF32Const:
|
||||
case kExprF64Const:
|
||||
case kExprMemorySize:
|
||||
return {0, 1};
|
||||
case kExprCallFunction: {
|
||||
CallFunctionOperand<true> operand(this, pc);
|
||||
CHECK(Complete(pc, operand));
|
||||
return {operand.sig->parameter_count(), operand.sig->return_count()};
|
||||
}
|
||||
case kExprCallIndirect: {
|
||||
CallIndirectOperand<true> operand(this, pc);
|
||||
CHECK(Complete(pc, operand));
|
||||
// Indirect calls pop an additional argument for the table index.
|
||||
return {operand.sig->parameter_count() + 1,
|
||||
operand.sig->return_count()};
|
||||
}
|
||||
case kExprBr:
|
||||
case kExprBlock:
|
||||
case kExprLoop:
|
||||
case kExprEnd:
|
||||
case kExprElse:
|
||||
case kExprNop:
|
||||
case kExprReturn:
|
||||
case kExprUnreachable:
|
||||
return {0, 0};
|
||||
default:
|
||||
V8_Fatal(__FILE__, __LINE__, "unimplemented opcode: %x", opcode);
|
||||
return {0, 0};
|
||||
}
|
||||
#undef DECLARE_OPCODE_CASE
|
||||
// clang-format on
|
||||
}
|
||||
};
|
||||
|
||||
static const int32_t kNullCatch = -1;
|
||||
@ -2055,6 +2117,13 @@ unsigned OpcodeLength(const byte* pc, const byte* end) {
|
||||
return WasmDecoder::OpcodeLength(&decoder, pc);
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> StackEffect(const WasmModule* module,
|
||||
FunctionSig* sig, const byte* pc,
|
||||
const byte* end) {
|
||||
WasmDecoder decoder(module, sig, pc, end);
|
||||
return decoder.StackEffect(pc);
|
||||
}
|
||||
|
||||
void PrintRawWasmCode(const byte* start, const byte* end) {
|
||||
AccountingAllocator allocator;
|
||||
PrintRawWasmCode(&allocator, FunctionBodyForTesting(start, end), nullptr);
|
||||
|
@ -81,12 +81,12 @@ struct BodyLocalDecls {
|
||||
|
||||
ZoneVector<ValueType> type_list;
|
||||
|
||||
// Constructor initializes the vector.
|
||||
explicit BodyLocalDecls(Zone* zone) : encoded_size(0), type_list(zone) {}
|
||||
};
|
||||
|
||||
V8_EXPORT_PRIVATE bool DecodeLocalDecls(BodyLocalDecls* decls,
|
||||
const byte* start, const byte* end);
|
||||
|
||||
V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone,
|
||||
size_t num_locals,
|
||||
const byte* start,
|
||||
@ -95,6 +95,15 @@ V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone,
|
||||
// Computes the length of the opcode at the given address.
|
||||
V8_EXPORT_PRIVATE unsigned OpcodeLength(const byte* pc, const byte* end);
|
||||
|
||||
// Computes the stack effect of the opcode at the given address.
|
||||
// Returns <pop count, push count>.
|
||||
// Be cautious with control opcodes: This function only covers their immediate,
|
||||
// local stack effect (e.g. BrIf pops 1, Br pops 0). Those opcodes can have
|
||||
// non-local stack effect though, which are not covered here.
|
||||
std::pair<uint32_t, uint32_t> StackEffect(const WasmModule* module,
|
||||
FunctionSig* sig, const byte* pc,
|
||||
const byte* end);
|
||||
|
||||
// A simple forward iterator for bytecodes.
|
||||
class V8_EXPORT_PRIVATE BytecodeIterator : public NON_EXPORTED_BASE(Decoder) {
|
||||
// Base class for both iterators defined below.
|
||||
|
@ -694,160 +694,7 @@ Handle<HeapObject> UnwrapWasmToJSWrapper(Isolate* isolate,
|
||||
return Handle<HeapObject>::null();
|
||||
}
|
||||
|
||||
// A helper class to compute the control transfers for each bytecode offset.
|
||||
// Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to
|
||||
// be directly executed without the need to dynamically track blocks.
|
||||
class ControlTransfers : public ZoneObject {
|
||||
public:
|
||||
ControlTransferMap map_;
|
||||
|
||||
ControlTransfers(Zone* zone, BodyLocalDecls* locals, const byte* start,
|
||||
const byte* end)
|
||||
: map_(zone) {
|
||||
// Represents a control flow label.
|
||||
struct CLabel : public ZoneObject {
|
||||
const byte* target;
|
||||
ZoneVector<const byte*> refs;
|
||||
|
||||
explicit CLabel(Zone* zone) : target(nullptr), refs(zone) {}
|
||||
|
||||
// Bind this label to the given PC.
|
||||
void Bind(ControlTransferMap* map, const byte* start, const byte* pc) {
|
||||
DCHECK_NULL(target);
|
||||
target = pc;
|
||||
for (auto from_pc : refs) {
|
||||
auto pcdiff = static_cast<pcdiff_t>(target - from_pc);
|
||||
size_t offset = static_cast<size_t>(from_pc - start);
|
||||
(*map)[offset] = pcdiff;
|
||||
}
|
||||
}
|
||||
|
||||
// Reference this label from the given location.
|
||||
void Ref(ControlTransferMap* map, const byte* start,
|
||||
const byte* from_pc) {
|
||||
if (target) {
|
||||
// Target being bound before a reference means this is a loop.
|
||||
DCHECK_EQ(kExprLoop, *target);
|
||||
auto pcdiff = static_cast<pcdiff_t>(target - from_pc);
|
||||
size_t offset = static_cast<size_t>(from_pc - start);
|
||||
(*map)[offset] = pcdiff;
|
||||
} else {
|
||||
refs.push_back(from_pc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// An entry in the control stack.
|
||||
struct Control {
|
||||
const byte* pc;
|
||||
CLabel* end_label;
|
||||
CLabel* else_label;
|
||||
|
||||
void Ref(ControlTransferMap* map, const byte* start,
|
||||
const byte* from_pc) {
|
||||
end_label->Ref(map, start, from_pc);
|
||||
}
|
||||
};
|
||||
|
||||
// Compute the ControlTransfer map.
|
||||
// This algorithm maintains a stack of control constructs similar to the
|
||||
// AST decoder. The {control_stack} allows matching {br,br_if,br_table}
|
||||
// bytecodes with their target, as well as determining whether the current
|
||||
// bytecodes are within the true or false block of an else.
|
||||
std::vector<Control> control_stack;
|
||||
CLabel* func_label = new (zone) CLabel(zone);
|
||||
control_stack.push_back({start, func_label, nullptr});
|
||||
for (BytecodeIterator i(start, end, locals); i.has_next(); i.next()) {
|
||||
WasmOpcode opcode = i.current();
|
||||
TRACE("@%u: control %s\n", i.pc_offset(),
|
||||
WasmOpcodes::OpcodeName(opcode));
|
||||
switch (opcode) {
|
||||
case kExprBlock: {
|
||||
TRACE("control @%u: Block\n", i.pc_offset());
|
||||
CLabel* label = new (zone) CLabel(zone);
|
||||
control_stack.push_back({i.pc(), label, nullptr});
|
||||
break;
|
||||
}
|
||||
case kExprLoop: {
|
||||
TRACE("control @%u: Loop\n", i.pc_offset());
|
||||
CLabel* label = new (zone) CLabel(zone);
|
||||
control_stack.push_back({i.pc(), label, nullptr});
|
||||
label->Bind(&map_, start, i.pc());
|
||||
break;
|
||||
}
|
||||
case kExprIf: {
|
||||
TRACE("control @%u: If\n", i.pc_offset());
|
||||
CLabel* end_label = new (zone) CLabel(zone);
|
||||
CLabel* else_label = new (zone) CLabel(zone);
|
||||
control_stack.push_back({i.pc(), end_label, else_label});
|
||||
else_label->Ref(&map_, start, i.pc());
|
||||
break;
|
||||
}
|
||||
case kExprElse: {
|
||||
Control* c = &control_stack.back();
|
||||
TRACE("control @%u: Else\n", i.pc_offset());
|
||||
c->end_label->Ref(&map_, start, i.pc());
|
||||
DCHECK_NOT_NULL(c->else_label);
|
||||
c->else_label->Bind(&map_, start, i.pc() + 1);
|
||||
c->else_label = nullptr;
|
||||
break;
|
||||
}
|
||||
case kExprEnd: {
|
||||
Control* c = &control_stack.back();
|
||||
TRACE("control @%u: End\n", i.pc_offset());
|
||||
if (c->end_label->target) {
|
||||
// only loops have bound labels.
|
||||
DCHECK_EQ(kExprLoop, *c->pc);
|
||||
} else {
|
||||
if (c->else_label) c->else_label->Bind(&map_, start, i.pc());
|
||||
c->end_label->Bind(&map_, start, i.pc() + 1);
|
||||
}
|
||||
control_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
case kExprBr: {
|
||||
BreakDepthOperand<false> operand(&i, i.pc());
|
||||
TRACE("control @%u: Br[depth=%u]\n", i.pc_offset(), operand.depth);
|
||||
Control* c = &control_stack[control_stack.size() - operand.depth - 1];
|
||||
c->Ref(&map_, start, i.pc());
|
||||
break;
|
||||
}
|
||||
case kExprBrIf: {
|
||||
BreakDepthOperand<false> operand(&i, i.pc());
|
||||
TRACE("control @%u: BrIf[depth=%u]\n", i.pc_offset(), operand.depth);
|
||||
Control* c = &control_stack[control_stack.size() - operand.depth - 1];
|
||||
c->Ref(&map_, start, i.pc());
|
||||
break;
|
||||
}
|
||||
case kExprBrTable: {
|
||||
BranchTableOperand<false> operand(&i, i.pc());
|
||||
BranchTableIterator<false> iterator(&i, operand);
|
||||
TRACE("control @%u: BrTable[count=%u]\n", i.pc_offset(),
|
||||
operand.table_count);
|
||||
while (iterator.has_next()) {
|
||||
uint32_t j = iterator.cur_index();
|
||||
uint32_t target = iterator.next();
|
||||
Control* c = &control_stack[control_stack.size() - target - 1];
|
||||
c->Ref(&map_, start, i.pc() + j);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!func_label->target) func_label->Bind(&map_, start, end);
|
||||
}
|
||||
|
||||
pcdiff_t Lookup(pc_t from) {
|
||||
auto result = map_.find(from);
|
||||
if (result == map_.end()) {
|
||||
V8_Fatal(__FILE__, __LINE__, "no control target for pc %zu", from);
|
||||
}
|
||||
return result->second;
|
||||
}
|
||||
};
|
||||
class ControlTransfers;
|
||||
|
||||
// Code and metadata needed to execute a function.
|
||||
struct InterpreterCode {
|
||||
@ -862,6 +709,201 @@ struct InterpreterCode {
|
||||
const byte* at(pc_t pc) { return start + pc; }
|
||||
};
|
||||
|
||||
// A helper class to compute the control transfers for each bytecode offset.
|
||||
// Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to
|
||||
// be directly executed without the need to dynamically track blocks.
|
||||
class ControlTransfers : public ZoneObject {
|
||||
public:
|
||||
ControlTransferMap map_;
|
||||
|
||||
ControlTransfers(Zone* zone, const WasmModule* module, InterpreterCode* code)
|
||||
: map_(zone) {
|
||||
// Create a zone for all temporary objects.
|
||||
Zone control_transfer_zone(zone->allocator(), ZONE_NAME);
|
||||
|
||||
// Represents a control flow label.
|
||||
class CLabel : public ZoneObject {
|
||||
explicit CLabel(Zone* zone, uint32_t target_stack_height, uint32_t arity)
|
||||
: target(nullptr),
|
||||
target_stack_height(target_stack_height),
|
||||
arity(arity),
|
||||
refs(zone) {}
|
||||
|
||||
public:
|
||||
struct Ref {
|
||||
const byte* from_pc;
|
||||
const uint32_t stack_height;
|
||||
};
|
||||
const byte* target;
|
||||
uint32_t target_stack_height;
|
||||
const uint32_t arity;
|
||||
// TODO(clemensh): Fix ZoneAllocator and make this ZoneVector<const Ref>.
|
||||
ZoneVector<Ref> refs;
|
||||
|
||||
static CLabel* New(Zone* zone, uint32_t stack_height, uint32_t arity) {
|
||||
return new (zone) CLabel(zone, stack_height, arity);
|
||||
}
|
||||
|
||||
// Bind this label to the given PC.
|
||||
void Bind(const byte* pc) {
|
||||
DCHECK_NULL(target);
|
||||
target = pc;
|
||||
}
|
||||
|
||||
// Reference this label from the given location.
|
||||
void Ref(const byte* from_pc, uint32_t stack_height) {
|
||||
// Target being bound before a reference means this is a loop.
|
||||
DCHECK_IMPLIES(target, *target == kExprLoop);
|
||||
refs.push_back({from_pc, stack_height});
|
||||
}
|
||||
|
||||
void Finish(ControlTransferMap* map, const byte* start) {
|
||||
DCHECK_NOT_NULL(target);
|
||||
for (auto ref : refs) {
|
||||
size_t offset = static_cast<size_t>(ref.from_pc - start);
|
||||
auto pcdiff = static_cast<pcdiff_t>(target - ref.from_pc);
|
||||
DCHECK_GE(ref.stack_height, target_stack_height);
|
||||
spdiff_t spdiff =
|
||||
static_cast<spdiff_t>(ref.stack_height - target_stack_height);
|
||||
TRACE("control transfer @%zu: Δpc %d, stack %u->%u = -%u\n", offset,
|
||||
pcdiff, ref.stack_height, target_stack_height, spdiff);
|
||||
ControlTransferEntry& entry = (*map)[offset];
|
||||
entry.pc_diff = pcdiff;
|
||||
entry.sp_diff = spdiff;
|
||||
entry.target_arity = arity;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// An entry in the control stack.
|
||||
struct Control {
|
||||
const byte* pc;
|
||||
CLabel* end_label;
|
||||
CLabel* else_label;
|
||||
|
||||
void Finish(ControlTransferMap* map, const byte* start) {
|
||||
end_label->Finish(map, start);
|
||||
if (else_label) else_label->Finish(map, start);
|
||||
}
|
||||
};
|
||||
|
||||
// Compute the ControlTransfer map.
|
||||
// This algorithm maintains a stack of control constructs similar to the
|
||||
// AST decoder. The {control_stack} allows matching {br,br_if,br_table}
|
||||
// bytecodes with their target, as well as determining whether the current
|
||||
// bytecodes are within the true or false block of an else.
|
||||
ZoneVector<Control> control_stack(&control_transfer_zone);
|
||||
uint32_t stack_height = 0;
|
||||
uint32_t func_arity =
|
||||
static_cast<uint32_t>(code->function->sig->return_count());
|
||||
CLabel* func_label =
|
||||
CLabel::New(&control_transfer_zone, stack_height, func_arity);
|
||||
control_stack.push_back({code->orig_start, func_label, nullptr});
|
||||
for (BytecodeIterator i(code->orig_start, code->orig_end, &code->locals);
|
||||
i.has_next(); i.next()) {
|
||||
WasmOpcode opcode = i.current();
|
||||
auto stack_effect =
|
||||
StackEffect(module, code->function->sig, i.pc(), i.end());
|
||||
TRACE("@%u: control %s (sp %d - %d + %d)\n", i.pc_offset(),
|
||||
WasmOpcodes::OpcodeName(opcode), stack_height, stack_effect.first,
|
||||
stack_effect.second);
|
||||
DCHECK_GE(stack_height, stack_effect.first);
|
||||
stack_height = stack_height - stack_effect.first + stack_effect.second;
|
||||
switch (opcode) {
|
||||
case kExprBlock:
|
||||
case kExprLoop: {
|
||||
bool loop = opcode == kExprLoop;
|
||||
BlockTypeOperand<false> operand(&i, i.pc());
|
||||
TRACE("control @%u: %s, arity %d\n", i.pc_offset(),
|
||||
loop ? "Loop" : "Block", operand.arity);
|
||||
CLabel* label =
|
||||
CLabel::New(&control_transfer_zone, stack_height, operand.arity);
|
||||
control_stack.push_back({i.pc(), label, nullptr});
|
||||
if (loop) label->Bind(i.pc());
|
||||
break;
|
||||
}
|
||||
case kExprIf: {
|
||||
TRACE("control @%u: If\n", i.pc_offset());
|
||||
BlockTypeOperand<false> operand(&i, i.pc());
|
||||
CLabel* end_label =
|
||||
CLabel::New(&control_transfer_zone, stack_height, operand.arity);
|
||||
CLabel* else_label =
|
||||
CLabel::New(&control_transfer_zone, stack_height, 0);
|
||||
control_stack.push_back({i.pc(), end_label, else_label});
|
||||
else_label->Ref(i.pc(), stack_height);
|
||||
break;
|
||||
}
|
||||
case kExprElse: {
|
||||
Control* c = &control_stack.back();
|
||||
TRACE("control @%u: Else\n", i.pc_offset());
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
DCHECK_NOT_NULL(c->else_label);
|
||||
c->else_label->Bind(i.pc() + 1);
|
||||
c->else_label->Finish(&map_, code->orig_start);
|
||||
c->else_label = nullptr;
|
||||
DCHECK_GE(stack_height, c->end_label->target_stack_height);
|
||||
stack_height = c->end_label->target_stack_height;
|
||||
break;
|
||||
}
|
||||
case kExprEnd: {
|
||||
Control* c = &control_stack.back();
|
||||
TRACE("control @%u: End\n", i.pc_offset());
|
||||
// Only loops have bound labels.
|
||||
DCHECK_IMPLIES(c->end_label->target, *c->pc == kExprLoop);
|
||||
if (!c->end_label->target) {
|
||||
if (c->else_label) c->else_label->Bind(i.pc());
|
||||
c->end_label->Bind(i.pc() + 1);
|
||||
}
|
||||
c->Finish(&map_, code->orig_start);
|
||||
DCHECK_GE(stack_height, c->end_label->target_stack_height);
|
||||
stack_height =
|
||||
c->end_label->target_stack_height + c->end_label->arity;
|
||||
control_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
case kExprBr: {
|
||||
BreakDepthOperand<false> operand(&i, i.pc());
|
||||
TRACE("control @%u: Br[depth=%u]\n", i.pc_offset(), operand.depth);
|
||||
Control* c = &control_stack[control_stack.size() - operand.depth - 1];
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
break;
|
||||
}
|
||||
case kExprBrIf: {
|
||||
BreakDepthOperand<false> operand(&i, i.pc());
|
||||
TRACE("control @%u: BrIf[depth=%u]\n", i.pc_offset(), operand.depth);
|
||||
Control* c = &control_stack[control_stack.size() - operand.depth - 1];
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
break;
|
||||
}
|
||||
case kExprBrTable: {
|
||||
BranchTableOperand<false> operand(&i, i.pc());
|
||||
BranchTableIterator<false> iterator(&i, operand);
|
||||
TRACE("control @%u: BrTable[count=%u]\n", i.pc_offset(),
|
||||
operand.table_count);
|
||||
while (iterator.has_next()) {
|
||||
uint32_t j = iterator.cur_index();
|
||||
uint32_t target = iterator.next();
|
||||
Control* c = &control_stack[control_stack.size() - target - 1];
|
||||
c->end_label->Ref(i.pc() + j, stack_height);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(0, control_stack.size());
|
||||
DCHECK_EQ(func_arity, stack_height);
|
||||
}
|
||||
|
||||
ControlTransferEntry& Lookup(pc_t from) {
|
||||
auto result = map_.find(from);
|
||||
DCHECK(result != map_.end());
|
||||
return result->second;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExternalCallResult {
|
||||
enum Type {
|
||||
// The function should be executed inside this interpreter.
|
||||
@ -982,8 +1024,7 @@ class CodeMap {
|
||||
DCHECK_EQ(code->function->imported, code->start == nullptr);
|
||||
if (code->targets == nullptr && code->start != nullptr) {
|
||||
// Compute the control targets map and the local declarations.
|
||||
code->targets = new (zone_) ControlTransfers(
|
||||
zone_, &code->locals, code->orig_start, code->orig_end);
|
||||
code->targets = new (zone_) ControlTransfers(zone_, module_, code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
@ -1122,7 +1163,6 @@ class ThreadImpl {
|
||||
instance_(instance),
|
||||
stack_(zone),
|
||||
frames_(zone),
|
||||
blocks_(zone),
|
||||
activations_(zone) {}
|
||||
|
||||
//==========================================================================
|
||||
@ -1279,7 +1319,6 @@ class ThreadImpl {
|
||||
WasmInstance* instance_;
|
||||
ZoneVector<WasmVal> stack_;
|
||||
ZoneVector<Frame> frames_;
|
||||
ZoneVector<Block> blocks_;
|
||||
WasmInterpreter::State state_ = WasmInterpreter::STOPPED;
|
||||
pc_t break_pc_ = kInvalidPc;
|
||||
TrapReason trap_reason_ = kTrapCount;
|
||||
@ -1308,9 +1347,6 @@ class ThreadImpl {
|
||||
// The parameters will overlap the arguments already on the stack.
|
||||
DCHECK_GE(stack_.size(), arity);
|
||||
frames_.push_back({code, 0, stack_.size() - arity});
|
||||
blocks_.push_back(
|
||||
{0, stack_.size(), frames_.size(),
|
||||
static_cast<uint32_t>(code->function->sig->return_count())});
|
||||
frames_.back().pc = InitLocals(code);
|
||||
TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1,
|
||||
code->function->func_index, frames_.back().pc);
|
||||
@ -1349,16 +1385,15 @@ class ThreadImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
int LookupTarget(InterpreterCode* code, pc_t pc) {
|
||||
return static_cast<int>(code->targets->Lookup(pc));
|
||||
int LookupTargetDelta(InterpreterCode* code, pc_t pc) {
|
||||
return static_cast<int>(code->targets->Lookup(pc).pc_diff);
|
||||
}
|
||||
|
||||
int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) {
|
||||
size_t bp = blocks_.size() - depth - 1;
|
||||
Block* target = &blocks_[bp];
|
||||
DoStackTransfer(target->sp, target->arity);
|
||||
blocks_.resize(bp);
|
||||
return LookupTarget(code, pc);
|
||||
ControlTransferEntry& control_transfer_entry = code->targets->Lookup(pc);
|
||||
DoStackTransfer(stack_.size() - control_transfer_entry.sp_diff,
|
||||
control_transfer_entry.target_arity);
|
||||
return control_transfer_entry.pc_diff;
|
||||
}
|
||||
|
||||
pc_t ReturnPc(Decoder* decoder, InterpreterCode* code, pc_t pc) {
|
||||
@ -1380,11 +1415,6 @@ class ThreadImpl {
|
||||
bool DoReturn(Decoder* decoder, InterpreterCode** code, pc_t* pc, pc_t* limit,
|
||||
size_t arity) {
|
||||
DCHECK_GT(frames_.size(), 0);
|
||||
// Pop all blocks for this frame.
|
||||
while (!blocks_.empty() && blocks_.back().fp == frames_.size()) {
|
||||
blocks_.pop_back();
|
||||
}
|
||||
|
||||
sp_t dest = frames_.back().sp;
|
||||
frames_.pop_back();
|
||||
if (frames_.size() == current_activation().fp) {
|
||||
@ -1551,18 +1581,27 @@ class ThreadImpl {
|
||||
TraceValueStack();
|
||||
TRACE("\n");
|
||||
|
||||
#ifdef DEBUG
|
||||
// Compute the stack effect of this opcode, and verify later that the
|
||||
// stack was modified accordingly.
|
||||
std::pair<uint32_t, uint32_t> stack_effect = wasm::StackEffect(
|
||||
codemap_->module(), frames_.back().code->function->sig,
|
||||
code->orig_start + pc, code->orig_end);
|
||||
uint32_t expected_new_stack_height =
|
||||
static_cast<uint32_t>(stack_.size()) - stack_effect.first +
|
||||
stack_effect.second;
|
||||
#endif
|
||||
|
||||
switch (orig) {
|
||||
case kExprNop:
|
||||
break;
|
||||
case kExprBlock: {
|
||||
BlockTypeOperand<false> operand(&decoder, code->at(pc));
|
||||
blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity});
|
||||
len = 1 + operand.length;
|
||||
break;
|
||||
}
|
||||
case kExprLoop: {
|
||||
BlockTypeOperand<false> operand(&decoder, code->at(pc));
|
||||
blocks_.push_back({pc, stack_.size(), frames_.size(), 0});
|
||||
len = 1 + operand.length;
|
||||
break;
|
||||
}
|
||||
@ -1570,20 +1609,18 @@ class ThreadImpl {
|
||||
BlockTypeOperand<false> operand(&decoder, code->at(pc));
|
||||
WasmVal cond = Pop();
|
||||
bool is_true = cond.to<uint32_t>() != 0;
|
||||
blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity});
|
||||
if (is_true) {
|
||||
// fall through to the true block.
|
||||
len = 1 + operand.length;
|
||||
TRACE(" true => fallthrough\n");
|
||||
} else {
|
||||
len = LookupTarget(code, pc);
|
||||
len = LookupTargetDelta(code, pc);
|
||||
TRACE(" false => @%zu\n", pc + len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kExprElse: {
|
||||
blocks_.pop_back();
|
||||
len = LookupTarget(code, pc);
|
||||
len = LookupTargetDelta(code, pc);
|
||||
TRACE(" end => @%zu\n", pc + len);
|
||||
break;
|
||||
}
|
||||
@ -1637,7 +1674,6 @@ class ThreadImpl {
|
||||
return DoTrap(kTrapUnreachable, pc);
|
||||
}
|
||||
case kExprEnd: {
|
||||
blocks_.pop_back();
|
||||
break;
|
||||
}
|
||||
case kExprI32Const: {
|
||||
@ -1965,6 +2001,12 @@ class ThreadImpl {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (!WasmOpcodes::IsControlOpcode(static_cast<WasmOpcode>(opcode))) {
|
||||
DCHECK_EQ(expected_new_stack_height, stack_.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
pc += len;
|
||||
if (pc == limit) {
|
||||
// Fell off end of code; do an implicit return.
|
||||
@ -2485,8 +2527,16 @@ void WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function,
|
||||
}
|
||||
|
||||
ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting(
|
||||
Zone* zone, const byte* start, const byte* end) {
|
||||
ControlTransfers targets(zone, nullptr, start, end);
|
||||
Zone* zone, const WasmModule* module, const byte* start, const byte* end) {
|
||||
// Create some dummy structures, to avoid special-casing the implementation
|
||||
// just for testing.
|
||||
FunctionSig sig(0, 0, nullptr);
|
||||
WasmFunction function{&sig, 0, 0, 0, 0, 0, 0, false, false};
|
||||
InterpreterCode code{
|
||||
&function, BodyLocalDecls(zone), start, end, nullptr, nullptr, nullptr};
|
||||
|
||||
// Now compute and return the control transfers.
|
||||
ControlTransfers targets(zone, module, &code);
|
||||
return targets.map_;
|
||||
}
|
||||
|
||||
|
@ -21,16 +21,27 @@ namespace wasm {
|
||||
// forward declarations.
|
||||
struct ModuleBytesEnv;
|
||||
struct WasmFunction;
|
||||
struct WasmModule;
|
||||
class WasmInterpreterInternals;
|
||||
|
||||
typedef size_t pc_t;
|
||||
typedef size_t sp_t;
|
||||
typedef int32_t pcdiff_t;
|
||||
typedef uint32_t spdiff_t;
|
||||
using pc_t = size_t;
|
||||
using sp_t = size_t;
|
||||
using pcdiff_t = int32_t;
|
||||
using spdiff_t = uint32_t;
|
||||
|
||||
const pc_t kInvalidPc = 0x80000000;
|
||||
constexpr pc_t kInvalidPc = 0x80000000;
|
||||
|
||||
typedef ZoneMap<pc_t, pcdiff_t> ControlTransferMap;
|
||||
struct ControlTransferEntry {
|
||||
// Distance from the instruction to the label to jump to (forward, but can be
|
||||
// negative).
|
||||
pcdiff_t pc_diff;
|
||||
// Delta by which to decrease the stack height.
|
||||
spdiff_t sp_diff;
|
||||
// Arity of the block we jump to.
|
||||
uint32_t target_arity;
|
||||
};
|
||||
|
||||
using ControlTransferMap = ZoneMap<pc_t, ControlTransferEntry>;
|
||||
|
||||
// Macro for defining union members.
|
||||
#define FOREACH_UNION_MEMBER(V) \
|
||||
@ -258,9 +269,8 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
|
||||
|
||||
// Computes the control transfers for the given bytecode. Used internally in
|
||||
// the interpreter, but exposed for testing.
|
||||
static ControlTransferMap ComputeControlTransfersForTesting(Zone* zone,
|
||||
const byte* start,
|
||||
const byte* end);
|
||||
static ControlTransferMap ComputeControlTransfersForTesting(
|
||||
Zone* zone, const WasmModule* module, const byte* start, const byte* end);
|
||||
|
||||
private:
|
||||
Zone zone_;
|
||||
|
@ -286,6 +286,17 @@ bool WasmOpcodes::IsPrefixOpcode(WasmOpcode opcode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool WasmOpcodes::IsControlOpcode(WasmOpcode opcode) {
|
||||
switch (opcode) {
|
||||
#define CHECK_OPCODE(name, opcode, _) \
|
||||
case kExpr##name: \
|
||||
return true;
|
||||
FOREACH_CONTROL_OPCODE(CHECK_OPCODE)
|
||||
#undef CHECK_OPCODE
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const FunctionSig& sig) {
|
||||
if (sig.return_count() == 0) os << "v";
|
||||
|
@ -597,6 +597,7 @@ class V8_EXPORT_PRIVATE WasmOpcodes {
|
||||
static FunctionSig* AsmjsSignature(WasmOpcode opcode);
|
||||
static FunctionSig* AtomicSignature(WasmOpcode opcode);
|
||||
static bool IsPrefixOpcode(WasmOpcode opcode);
|
||||
static bool IsControlOpcode(WasmOpcode opcode);
|
||||
|
||||
static int TrapReasonToMessageId(TrapReason reason);
|
||||
static const char* TrapReasonMessage(TrapReason reason);
|
||||
|
@ -27,68 +27,75 @@ namespace wasm {
|
||||
#define TRANSFER_VOID 0
|
||||
#define TRANSFER_ONE 1
|
||||
|
||||
struct ExpectedPcDelta {
|
||||
struct ExpectedControlTransfer {
|
||||
pc_t pc;
|
||||
pcdiff_t expected;
|
||||
pcdiff_t pc_diff;
|
||||
uint32_t sp_diff;
|
||||
uint32_t target_arity;
|
||||
};
|
||||
|
||||
// For nicer error messages.
|
||||
class ControlTransferMatcher : public MatcherInterface<const pcdiff_t&> {
|
||||
class ControlTransferMatcher
|
||||
: public MatcherInterface<const ControlTransferEntry&> {
|
||||
public:
|
||||
explicit ControlTransferMatcher(pc_t pc, const pcdiff_t& expected)
|
||||
explicit ControlTransferMatcher(pc_t pc,
|
||||
const ExpectedControlTransfer& expected)
|
||||
: pc_(pc), expected_(expected) {}
|
||||
|
||||
void DescribeTo(std::ostream* os) const override {
|
||||
*os << "@" << pc_ << " pcdiff = " << expected_;
|
||||
*os << "@" << pc_ << ": pcdiff = " << expected_.pc_diff
|
||||
<< ", spdiff = " << expected_.sp_diff
|
||||
<< ", target arity = " << expected_.target_arity;
|
||||
}
|
||||
|
||||
bool MatchAndExplain(const pcdiff_t& input,
|
||||
bool MatchAndExplain(const ControlTransferEntry& input,
|
||||
MatchResultListener* listener) const override {
|
||||
if (input != expected_) {
|
||||
*listener << "@" << pc_ << " pcdiff = " << input;
|
||||
return false;
|
||||
if (input.pc_diff == expected_.pc_diff &&
|
||||
input.sp_diff == expected_.sp_diff &&
|
||||
input.target_arity == expected_.target_arity) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
*listener << "@" << pc_ << ": pcdiff = " << input.pc_diff
|
||||
<< ", spdiff = " << input.sp_diff
|
||||
<< ", target arity = " << input.target_arity;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
pc_t pc_;
|
||||
const pcdiff_t& expected_;
|
||||
const ExpectedControlTransfer& expected_;
|
||||
};
|
||||
|
||||
class ControlTransferTest : public TestWithZone {
|
||||
public:
|
||||
template <int code_len>
|
||||
void CheckPcDeltas(const byte (&code)[code_len],
|
||||
std::initializer_list<ExpectedPcDelta> expected_deltas) {
|
||||
void CheckTransfers(
|
||||
const byte (&code)[code_len],
|
||||
std::initializer_list<ExpectedControlTransfer> expected_transfers) {
|
||||
byte code_with_end[code_len + 1]; // NOLINT: code_len is a constant here
|
||||
memcpy(code_with_end, code, code_len);
|
||||
code_with_end[code_len] = kExprEnd;
|
||||
|
||||
ControlTransferMap map = WasmInterpreter::ComputeControlTransfersForTesting(
|
||||
zone(), code_with_end, code_with_end + code_len + 1);
|
||||
zone(), nullptr, code_with_end, code_with_end + code_len + 1);
|
||||
// Check all control targets in the map.
|
||||
for (auto& expected_delta : expected_deltas) {
|
||||
pc_t pc = expected_delta.pc;
|
||||
auto it = map.find(pc);
|
||||
if (it == map.end()) {
|
||||
EXPECT_TRUE(false) << "expected control target @" << pc;
|
||||
} else {
|
||||
pcdiff_t expected = expected_delta.expected;
|
||||
pcdiff_t& target = it->second;
|
||||
EXPECT_THAT(target,
|
||||
MakeMatcher(new ControlTransferMatcher(pc, expected)));
|
||||
}
|
||||
for (auto& expected_transfer : expected_transfers) {
|
||||
pc_t pc = expected_transfer.pc;
|
||||
EXPECT_TRUE(map.count(pc) > 0) << "expected control target @" << pc;
|
||||
if (!map.count(pc)) continue;
|
||||
auto& entry = map[pc];
|
||||
EXPECT_THAT(entry, MakeMatcher(new ControlTransferMatcher(
|
||||
pc, expected_transfer)));
|
||||
}
|
||||
|
||||
// Check there are no other control targets.
|
||||
CheckNoOtherTargets(code_with_end, code_with_end + code_len + 1, map,
|
||||
expected_deltas);
|
||||
expected_transfers);
|
||||
}
|
||||
|
||||
void CheckNoOtherTargets(const byte* start, const byte* end,
|
||||
ControlTransferMap& map,
|
||||
std::initializer_list<ExpectedPcDelta> targets) {
|
||||
void CheckNoOtherTargets(
|
||||
const byte* start, const byte* end, ControlTransferMap& map,
|
||||
std::initializer_list<ExpectedControlTransfer> targets) {
|
||||
// Check there are no other control targets.
|
||||
for (pc_t pc = 0; start + pc < end; pc++) {
|
||||
bool found = false;
|
||||
@ -112,7 +119,7 @@ TEST_F(ControlTransferTest, SimpleIf) {
|
||||
kLocalVoid, // @3
|
||||
kExprEnd // @4
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 2}});
|
||||
CheckTransfers(code, {{2, 2, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleIf1) {
|
||||
@ -124,7 +131,7 @@ TEST_F(ControlTransferTest, SimpleIf1) {
|
||||
kExprNop, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 3}});
|
||||
CheckTransfers(code, {{2, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleIf2) {
|
||||
@ -137,7 +144,7 @@ TEST_F(ControlTransferTest, SimpleIf2) {
|
||||
kExprNop, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 4}});
|
||||
CheckTransfers(code, {{2, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleIfElse) {
|
||||
@ -149,7 +156,7 @@ TEST_F(ControlTransferTest, SimpleIfElse) {
|
||||
kExprElse, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 3}, {4, 2}});
|
||||
CheckTransfers(code, {{2, 3, 0, 0}, {4, 2, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleIfElse_v1) {
|
||||
@ -165,7 +172,7 @@ TEST_F(ControlTransferTest, SimpleIfElse_v1) {
|
||||
0, // @8
|
||||
kExprEnd // @9
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 5}, {6, 4}});
|
||||
CheckTransfers(code, {{2, 5, 0, 0}, {6, 4, 1, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleIfElse1) {
|
||||
@ -178,7 +185,7 @@ TEST_F(ControlTransferTest, SimpleIfElse1) {
|
||||
kExprNop, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 3}, {4, 3}});
|
||||
CheckTransfers(code, {{2, 3, 0, 0}, {4, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, IfBr) {
|
||||
@ -191,7 +198,7 @@ TEST_F(ControlTransferTest, IfBr) {
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 4}, {4, 3}});
|
||||
CheckTransfers(code, {{2, 4, 0, 0}, {4, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, IfBrElse) {
|
||||
@ -205,7 +212,7 @@ TEST_F(ControlTransferTest, IfBrElse) {
|
||||
kExprElse, // @6
|
||||
kExprEnd // @7
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 5}, {4, 4}, {6, 2}});
|
||||
CheckTransfers(code, {{2, 5, 0, 0}, {4, 4, 0, 0}, {6, 2, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, IfElseBr) {
|
||||
@ -219,15 +226,16 @@ TEST_F(ControlTransferTest, IfElseBr) {
|
||||
0, // @6
|
||||
kExprEnd // @7
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 3}, {4, 4}, {5, 3}});
|
||||
CheckTransfers(code, {{2, 3, 0, 0}, {4, 4, 0, 0}, {5, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BlockEmpty) {
|
||||
byte code[] = {
|
||||
kExprBlock, // @0
|
||||
kExprEnd // @1
|
||||
kLocalVoid, // @1
|
||||
kExprEnd // @2
|
||||
};
|
||||
CheckPcDeltas(code, {});
|
||||
CheckTransfers(code, {});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br0) {
|
||||
@ -238,7 +246,7 @@ TEST_F(ControlTransferTest, Br0) {
|
||||
0, // @3
|
||||
kExprEnd // @4
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 3}});
|
||||
CheckTransfers(code, {{2, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br1) {
|
||||
@ -250,7 +258,7 @@ TEST_F(ControlTransferTest, Br1) {
|
||||
0, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{3, 3}});
|
||||
CheckTransfers(code, {{3, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br_v1a) {
|
||||
@ -263,7 +271,7 @@ TEST_F(ControlTransferTest, Br_v1a) {
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 3}});
|
||||
CheckTransfers(code, {{4, 3, 1, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br_v1b) {
|
||||
@ -276,7 +284,7 @@ TEST_F(ControlTransferTest, Br_v1b) {
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 3}});
|
||||
CheckTransfers(code, {{4, 3, 1, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br_v1c) {
|
||||
@ -289,7 +297,20 @@ TEST_F(ControlTransferTest, Br_v1c) {
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 3}});
|
||||
CheckTransfers(code, {{4, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br_v1d) {
|
||||
byte code[] = {
|
||||
kExprBlock, // @0
|
||||
kLocalI32, // @1
|
||||
kExprI32Const, // @2
|
||||
0, // @3
|
||||
kExprBr, // @4
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckTransfers(code, {{4, 3, 1, 1}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br2) {
|
||||
@ -302,7 +323,7 @@ TEST_F(ControlTransferTest, Br2) {
|
||||
0, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 3}});
|
||||
CheckTransfers(code, {{4, 3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br0b) {
|
||||
@ -314,7 +335,7 @@ TEST_F(ControlTransferTest, Br0b) {
|
||||
kExprNop, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 4}});
|
||||
CheckTransfers(code, {{2, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, Br0c) {
|
||||
@ -327,7 +348,7 @@ TEST_F(ControlTransferTest, Br0c) {
|
||||
kExprNop, // @5
|
||||
kExprEnd // @6
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 5}});
|
||||
CheckTransfers(code, {{2, 5, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleLoop1) {
|
||||
@ -338,7 +359,7 @@ TEST_F(ControlTransferTest, SimpleLoop1) {
|
||||
0, // @3
|
||||
kExprEnd // @4
|
||||
};
|
||||
CheckPcDeltas(code, {{2, -2}});
|
||||
CheckTransfers(code, {{2, -2, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleLoop2) {
|
||||
@ -350,7 +371,7 @@ TEST_F(ControlTransferTest, SimpleLoop2) {
|
||||
0, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{3, -3}});
|
||||
CheckTransfers(code, {{3, -3, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleLoopExit1) {
|
||||
@ -361,7 +382,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit1) {
|
||||
1, // @3
|
||||
kExprEnd // @4
|
||||
};
|
||||
CheckPcDeltas(code, {{2, 4}});
|
||||
CheckTransfers(code, {{2, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, SimpleLoopExit2) {
|
||||
@ -373,7 +394,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit2) {
|
||||
1, // @4
|
||||
kExprEnd // @5
|
||||
};
|
||||
CheckPcDeltas(code, {{3, 4}});
|
||||
CheckTransfers(code, {{3, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BrTable0) {
|
||||
@ -387,7 +408,7 @@ TEST_F(ControlTransferTest, BrTable0) {
|
||||
U32V_1(0), // @6
|
||||
kExprEnd // @7
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 4}});
|
||||
CheckTransfers(code, {{4, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BrTable0_v1a) {
|
||||
@ -403,7 +424,7 @@ TEST_F(ControlTransferTest, BrTable0_v1a) {
|
||||
U32V_1(0), // @8
|
||||
kExprEnd // @9
|
||||
};
|
||||
CheckPcDeltas(code, {{6, 4}});
|
||||
CheckTransfers(code, {{6, 4, 1, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BrTable0_v1b) {
|
||||
@ -419,7 +440,7 @@ TEST_F(ControlTransferTest, BrTable0_v1b) {
|
||||
U32V_1(0), // @8
|
||||
kExprEnd // @9
|
||||
};
|
||||
CheckPcDeltas(code, {{6, 4}});
|
||||
CheckTransfers(code, {{6, 4, 1, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BrTable1) {
|
||||
@ -434,7 +455,7 @@ TEST_F(ControlTransferTest, BrTable1) {
|
||||
U32V_1(0), // @7
|
||||
kExprEnd // @8
|
||||
};
|
||||
CheckPcDeltas(code, {{4, 5}, {5, 4}});
|
||||
CheckTransfers(code, {{4, 5, 0, 0}, {5, 4, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BrTable2) {
|
||||
@ -453,7 +474,29 @@ TEST_F(ControlTransferTest, BrTable2) {
|
||||
kExprEnd, // @11
|
||||
kExprEnd // @12
|
||||
};
|
||||
CheckPcDeltas(code, {{6, 6}, {7, 5}, {8, 5}});
|
||||
CheckTransfers(code, {{6, 6, 0, 0}, {7, 5, 0, 0}, {8, 5, 0, 0}});
|
||||
}
|
||||
|
||||
TEST_F(ControlTransferTest, BiggerSpDiffs) {
|
||||
byte code[] = {
|
||||
kExprBlock, // @0
|
||||
kLocalI32, // @1
|
||||
kExprI32Const, // @2
|
||||
0, // @3
|
||||
kExprBlock, // @4
|
||||
kLocalVoid, // @5
|
||||
kExprI32Const, // @6
|
||||
0, // @7
|
||||
kExprI32Const, // @8
|
||||
0, // @9
|
||||
kExprBr, // @10
|
||||
0, // @11
|
||||
kExprBr, // @12
|
||||
1, // @13
|
||||
kExprEnd, // @14
|
||||
kExprEnd // @15
|
||||
};
|
||||
CheckTransfers(code, {{10, 5, 2, 0}, {12, 4, 3, 1}});
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
Loading…
Reference in New Issue
Block a user