Commit 92bf8327 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[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/485482Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44837}
parent c2abd807
...@@ -515,6 +515,68 @@ class WasmDecoder : public Decoder { ...@@ -515,6 +515,68 @@ class WasmDecoder : public Decoder {
return 1; 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; static const int32_t kNullCatch = -1;
...@@ -2055,6 +2117,13 @@ unsigned OpcodeLength(const byte* pc, const byte* end) { ...@@ -2055,6 +2117,13 @@ unsigned OpcodeLength(const byte* pc, const byte* end) {
return WasmDecoder::OpcodeLength(&decoder, pc); 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) { void PrintRawWasmCode(const byte* start, const byte* end) {
AccountingAllocator allocator; AccountingAllocator allocator;
PrintRawWasmCode(&allocator, FunctionBodyForTesting(start, end), nullptr); PrintRawWasmCode(&allocator, FunctionBodyForTesting(start, end), nullptr);
......
...@@ -81,12 +81,12 @@ struct BodyLocalDecls { ...@@ -81,12 +81,12 @@ struct BodyLocalDecls {
ZoneVector<ValueType> type_list; ZoneVector<ValueType> type_list;
// Constructor initializes the vector.
explicit BodyLocalDecls(Zone* zone) : encoded_size(0), type_list(zone) {} explicit BodyLocalDecls(Zone* zone) : encoded_size(0), type_list(zone) {}
}; };
V8_EXPORT_PRIVATE bool DecodeLocalDecls(BodyLocalDecls* decls, V8_EXPORT_PRIVATE bool DecodeLocalDecls(BodyLocalDecls* decls,
const byte* start, const byte* end); const byte* start, const byte* end);
V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone,
size_t num_locals, size_t num_locals,
const byte* start, const byte* start,
...@@ -95,6 +95,15 @@ V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, ...@@ -95,6 +95,15 @@ V8_EXPORT_PRIVATE BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone,
// Computes the length of the opcode at the given address. // Computes the length of the opcode at the given address.
V8_EXPORT_PRIVATE unsigned OpcodeLength(const byte* pc, const byte* end); 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. // A simple forward iterator for bytecodes.
class V8_EXPORT_PRIVATE BytecodeIterator : public NON_EXPORTED_BASE(Decoder) { class V8_EXPORT_PRIVATE BytecodeIterator : public NON_EXPORTED_BASE(Decoder) {
// Base class for both iterators defined below. // Base class for both iterators defined below.
......
...@@ -694,6 +694,21 @@ Handle<HeapObject> UnwrapWasmToJSWrapper(Isolate* isolate, ...@@ -694,6 +694,21 @@ Handle<HeapObject> UnwrapWasmToJSWrapper(Isolate* isolate,
return Handle<HeapObject>::null(); return Handle<HeapObject>::null();
} }
class ControlTransfers;
// Code and metadata needed to execute a function.
struct InterpreterCode {
const WasmFunction* function; // wasm function
BodyLocalDecls locals; // local declarations
const byte* orig_start; // start of original code
const byte* orig_end; // end of original code
byte* start; // start of (maybe altered) code
byte* end; // end of (maybe altered) code
ControlTransfers* targets; // helper for control flow.
const byte* at(pc_t pc) { return start + pc; }
};
// A helper class to compute the control transfers for each bytecode offset. // A helper class to compute the control transfers for each bytecode offset.
// Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to // Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to
// be directly executed without the need to dynamically track blocks. // be directly executed without the need to dynamically track blocks.
...@@ -701,38 +716,61 @@ class ControlTransfers : public ZoneObject { ...@@ -701,38 +716,61 @@ class ControlTransfers : public ZoneObject {
public: public:
ControlTransferMap map_; ControlTransferMap map_;
ControlTransfers(Zone* zone, BodyLocalDecls* locals, const byte* start, ControlTransfers(Zone* zone, const WasmModule* module, InterpreterCode* code)
const byte* end)
: map_(zone) { : map_(zone) {
// Create a zone for all temporary objects.
Zone control_transfer_zone(zone->allocator(), ZONE_NAME);
// Represents a control flow label. // Represents a control flow label.
struct CLabel : public ZoneObject { 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; const byte* target;
ZoneVector<const byte*> refs; uint32_t target_stack_height;
const uint32_t arity;
// TODO(clemensh): Fix ZoneAllocator and make this ZoneVector<const Ref>.
ZoneVector<Ref> refs;
explicit CLabel(Zone* zone) : target(nullptr), refs(zone) {} 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. // Bind this label to the given PC.
void Bind(ControlTransferMap* map, const byte* start, const byte* pc) { void Bind(const byte* pc) {
DCHECK_NULL(target); DCHECK_NULL(target);
target = pc; 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. // Reference this label from the given location.
void Ref(ControlTransferMap* map, const byte* start, void Ref(const byte* from_pc, uint32_t stack_height) {
const byte* from_pc) { // Target being bound before a reference means this is a loop.
if (target) { DCHECK_IMPLIES(target, *target == kExprLoop);
// Target being bound before a reference means this is a loop. refs.push_back({from_pc, stack_height});
DCHECK_EQ(kExprLoop, *target); }
auto pcdiff = static_cast<pcdiff_t>(target - from_pc);
size_t offset = static_cast<size_t>(from_pc - start); void Finish(ControlTransferMap* map, const byte* start) {
(*map)[offset] = pcdiff; DCHECK_NOT_NULL(target);
} else { for (auto ref : refs) {
refs.push_back(from_pc); 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;
} }
} }
}; };
...@@ -743,9 +781,9 @@ class ControlTransfers : public ZoneObject { ...@@ -743,9 +781,9 @@ class ControlTransfers : public ZoneObject {
CLabel* end_label; CLabel* end_label;
CLabel* else_label; CLabel* else_label;
void Ref(ControlTransferMap* map, const byte* start, void Finish(ControlTransferMap* map, const byte* start) {
const byte* from_pc) { end_label->Finish(map, start);
end_label->Ref(map, start, from_pc); if (else_label) else_label->Finish(map, start);
} }
}; };
...@@ -754,54 +792,72 @@ class ControlTransfers : public ZoneObject { ...@@ -754,54 +792,72 @@ class ControlTransfers : public ZoneObject {
// AST decoder. The {control_stack} allows matching {br,br_if,br_table} // AST decoder. The {control_stack} allows matching {br,br_if,br_table}
// bytecodes with their target, as well as determining whether the current // bytecodes with their target, as well as determining whether the current
// bytecodes are within the true or false block of an else. // bytecodes are within the true or false block of an else.
std::vector<Control> control_stack; ZoneVector<Control> control_stack(&control_transfer_zone);
CLabel* func_label = new (zone) CLabel(zone); uint32_t stack_height = 0;
control_stack.push_back({start, func_label, nullptr}); uint32_t func_arity =
for (BytecodeIterator i(start, end, locals); i.has_next(); i.next()) { 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(); WasmOpcode opcode = i.current();
TRACE("@%u: control %s\n", i.pc_offset(), auto stack_effect =
WasmOpcodes::OpcodeName(opcode)); 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) { switch (opcode) {
case kExprBlock: { 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: { case kExprLoop: {
TRACE("control @%u: Loop\n", i.pc_offset()); bool loop = opcode == kExprLoop;
CLabel* label = new (zone) CLabel(zone); 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}); control_stack.push_back({i.pc(), label, nullptr});
label->Bind(&map_, start, i.pc()); if (loop) label->Bind(i.pc());
break; break;
} }
case kExprIf: { case kExprIf: {
TRACE("control @%u: If\n", i.pc_offset()); TRACE("control @%u: If\n", i.pc_offset());
CLabel* end_label = new (zone) CLabel(zone); BlockTypeOperand<false> operand(&i, i.pc());
CLabel* else_label = new (zone) CLabel(zone); 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}); control_stack.push_back({i.pc(), end_label, else_label});
else_label->Ref(&map_, start, i.pc()); else_label->Ref(i.pc(), stack_height);
break; break;
} }
case kExprElse: { case kExprElse: {
Control* c = &control_stack.back(); Control* c = &control_stack.back();
TRACE("control @%u: Else\n", i.pc_offset()); TRACE("control @%u: Else\n", i.pc_offset());
c->end_label->Ref(&map_, start, i.pc()); c->end_label->Ref(i.pc(), stack_height);
DCHECK_NOT_NULL(c->else_label); DCHECK_NOT_NULL(c->else_label);
c->else_label->Bind(&map_, start, i.pc() + 1); c->else_label->Bind(i.pc() + 1);
c->else_label->Finish(&map_, code->orig_start);
c->else_label = nullptr; c->else_label = nullptr;
DCHECK_GE(stack_height, c->end_label->target_stack_height);
stack_height = c->end_label->target_stack_height;
break; break;
} }
case kExprEnd: { case kExprEnd: {
Control* c = &control_stack.back(); Control* c = &control_stack.back();
TRACE("control @%u: End\n", i.pc_offset()); TRACE("control @%u: End\n", i.pc_offset());
if (c->end_label->target) { // Only loops have bound labels.
// only loops have bound labels. DCHECK_IMPLIES(c->end_label->target, *c->pc == kExprLoop);
DCHECK_EQ(kExprLoop, *c->pc); if (!c->end_label->target) {
} else { if (c->else_label) c->else_label->Bind(i.pc());
if (c->else_label) c->else_label->Bind(&map_, start, i.pc()); c->end_label->Bind(i.pc() + 1);
c->end_label->Bind(&map_, start, 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(); control_stack.pop_back();
break; break;
} }
...@@ -809,14 +865,14 @@ class ControlTransfers : public ZoneObject { ...@@ -809,14 +865,14 @@ class ControlTransfers : public ZoneObject {
BreakDepthOperand<false> operand(&i, i.pc()); BreakDepthOperand<false> operand(&i, i.pc());
TRACE("control @%u: Br[depth=%u]\n", i.pc_offset(), operand.depth); TRACE("control @%u: Br[depth=%u]\n", i.pc_offset(), operand.depth);
Control* c = &control_stack[control_stack.size() - operand.depth - 1]; Control* c = &control_stack[control_stack.size() - operand.depth - 1];
c->Ref(&map_, start, i.pc()); c->end_label->Ref(i.pc(), stack_height);
break; break;
} }
case kExprBrIf: { case kExprBrIf: {
BreakDepthOperand<false> operand(&i, i.pc()); BreakDepthOperand<false> operand(&i, i.pc());
TRACE("control @%u: BrIf[depth=%u]\n", i.pc_offset(), operand.depth); TRACE("control @%u: BrIf[depth=%u]\n", i.pc_offset(), operand.depth);
Control* c = &control_stack[control_stack.size() - operand.depth - 1]; Control* c = &control_stack[control_stack.size() - operand.depth - 1];
c->Ref(&map_, start, i.pc()); c->end_label->Ref(i.pc(), stack_height);
break; break;
} }
case kExprBrTable: { case kExprBrTable: {
...@@ -828,7 +884,7 @@ class ControlTransfers : public ZoneObject { ...@@ -828,7 +884,7 @@ class ControlTransfers : public ZoneObject {
uint32_t j = iterator.cur_index(); uint32_t j = iterator.cur_index();
uint32_t target = iterator.next(); uint32_t target = iterator.next();
Control* c = &control_stack[control_stack.size() - target - 1]; Control* c = &control_stack[control_stack.size() - target - 1];
c->Ref(&map_, start, i.pc() + j); c->end_label->Ref(i.pc() + j, stack_height);
} }
break; break;
} }
...@@ -837,31 +893,17 @@ class ControlTransfers : public ZoneObject { ...@@ -837,31 +893,17 @@ class ControlTransfers : public ZoneObject {
} }
} }
} }
if (!func_label->target) func_label->Bind(&map_, start, end); DCHECK_EQ(0, control_stack.size());
DCHECK_EQ(func_arity, stack_height);
} }
pcdiff_t Lookup(pc_t from) { ControlTransferEntry& Lookup(pc_t from) {
auto result = map_.find(from); auto result = map_.find(from);
if (result == map_.end()) { DCHECK(result != map_.end());
V8_Fatal(__FILE__, __LINE__, "no control target for pc %zu", from);
}
return result->second; return result->second;
} }
}; };
// Code and metadata needed to execute a function.
struct InterpreterCode {
const WasmFunction* function; // wasm function
BodyLocalDecls locals; // local declarations
const byte* orig_start; // start of original code
const byte* orig_end; // end of original code
byte* start; // start of (maybe altered) code
byte* end; // end of (maybe altered) code
ControlTransfers* targets; // helper for control flow.
const byte* at(pc_t pc) { return start + pc; }
};
struct ExternalCallResult { struct ExternalCallResult {
enum Type { enum Type {
// The function should be executed inside this interpreter. // The function should be executed inside this interpreter.
...@@ -982,8 +1024,7 @@ class CodeMap { ...@@ -982,8 +1024,7 @@ class CodeMap {
DCHECK_EQ(code->function->imported, code->start == nullptr); DCHECK_EQ(code->function->imported, code->start == nullptr);
if (code->targets == nullptr && code->start != nullptr) { if (code->targets == nullptr && code->start != nullptr) {
// Compute the control targets map and the local declarations. // Compute the control targets map and the local declarations.
code->targets = new (zone_) ControlTransfers( code->targets = new (zone_) ControlTransfers(zone_, module_, code);
zone_, &code->locals, code->orig_start, code->orig_end);
} }
return code; return code;
} }
...@@ -1122,7 +1163,6 @@ class ThreadImpl { ...@@ -1122,7 +1163,6 @@ class ThreadImpl {
instance_(instance), instance_(instance),
stack_(zone), stack_(zone),
frames_(zone), frames_(zone),
blocks_(zone),
activations_(zone) {} activations_(zone) {}
//========================================================================== //==========================================================================
...@@ -1279,7 +1319,6 @@ class ThreadImpl { ...@@ -1279,7 +1319,6 @@ class ThreadImpl {
WasmInstance* instance_; WasmInstance* instance_;
ZoneVector<WasmVal> stack_; ZoneVector<WasmVal> stack_;
ZoneVector<Frame> frames_; ZoneVector<Frame> frames_;
ZoneVector<Block> blocks_;
WasmInterpreter::State state_ = WasmInterpreter::STOPPED; WasmInterpreter::State state_ = WasmInterpreter::STOPPED;
pc_t break_pc_ = kInvalidPc; pc_t break_pc_ = kInvalidPc;
TrapReason trap_reason_ = kTrapCount; TrapReason trap_reason_ = kTrapCount;
...@@ -1308,9 +1347,6 @@ class ThreadImpl { ...@@ -1308,9 +1347,6 @@ class ThreadImpl {
// The parameters will overlap the arguments already on the stack. // The parameters will overlap the arguments already on the stack.
DCHECK_GE(stack_.size(), arity); DCHECK_GE(stack_.size(), arity);
frames_.push_back({code, 0, 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); frames_.back().pc = InitLocals(code);
TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1, TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1,
code->function->func_index, frames_.back().pc); code->function->func_index, frames_.back().pc);
...@@ -1349,16 +1385,15 @@ class ThreadImpl { ...@@ -1349,16 +1385,15 @@ class ThreadImpl {
return false; return false;
} }
int LookupTarget(InterpreterCode* code, pc_t pc) { int LookupTargetDelta(InterpreterCode* code, pc_t pc) {
return static_cast<int>(code->targets->Lookup(pc)); return static_cast<int>(code->targets->Lookup(pc).pc_diff);
} }
int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) { int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) {
size_t bp = blocks_.size() - depth - 1; ControlTransferEntry& control_transfer_entry = code->targets->Lookup(pc);
Block* target = &blocks_[bp]; DoStackTransfer(stack_.size() - control_transfer_entry.sp_diff,
DoStackTransfer(target->sp, target->arity); control_transfer_entry.target_arity);
blocks_.resize(bp); return control_transfer_entry.pc_diff;
return LookupTarget(code, pc);
} }
pc_t ReturnPc(Decoder* decoder, InterpreterCode* code, pc_t pc) { pc_t ReturnPc(Decoder* decoder, InterpreterCode* code, pc_t pc) {
...@@ -1380,11 +1415,6 @@ class ThreadImpl { ...@@ -1380,11 +1415,6 @@ class ThreadImpl {
bool DoReturn(Decoder* decoder, InterpreterCode** code, pc_t* pc, pc_t* limit, bool DoReturn(Decoder* decoder, InterpreterCode** code, pc_t* pc, pc_t* limit,
size_t arity) { size_t arity) {
DCHECK_GT(frames_.size(), 0); 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; sp_t dest = frames_.back().sp;
frames_.pop_back(); frames_.pop_back();
if (frames_.size() == current_activation().fp) { if (frames_.size() == current_activation().fp) {
...@@ -1551,18 +1581,27 @@ class ThreadImpl { ...@@ -1551,18 +1581,27 @@ class ThreadImpl {
TraceValueStack(); TraceValueStack();
TRACE("\n"); 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) { switch (orig) {
case kExprNop: case kExprNop:
break; break;
case kExprBlock: { case kExprBlock: {
BlockTypeOperand<false> operand(&decoder, code->at(pc)); BlockTypeOperand<false> operand(&decoder, code->at(pc));
blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity});
len = 1 + operand.length; len = 1 + operand.length;
break; break;
} }
case kExprLoop: { case kExprLoop: {
BlockTypeOperand<false> operand(&decoder, code->at(pc)); BlockTypeOperand<false> operand(&decoder, code->at(pc));
blocks_.push_back({pc, stack_.size(), frames_.size(), 0});
len = 1 + operand.length; len = 1 + operand.length;
break; break;
} }
...@@ -1570,20 +1609,18 @@ class ThreadImpl { ...@@ -1570,20 +1609,18 @@ class ThreadImpl {
BlockTypeOperand<false> operand(&decoder, code->at(pc)); BlockTypeOperand<false> operand(&decoder, code->at(pc));
WasmVal cond = Pop(); WasmVal cond = Pop();
bool is_true = cond.to<uint32_t>() != 0; bool is_true = cond.to<uint32_t>() != 0;
blocks_.push_back({pc, stack_.size(), frames_.size(), operand.arity});
if (is_true) { if (is_true) {
// fall through to the true block. // fall through to the true block.
len = 1 + operand.length; len = 1 + operand.length;
TRACE(" true => fallthrough\n"); TRACE(" true => fallthrough\n");
} else { } else {
len = LookupTarget(code, pc); len = LookupTargetDelta(code, pc);
TRACE(" false => @%zu\n", pc + len); TRACE(" false => @%zu\n", pc + len);
} }
break; break;
} }
case kExprElse: { case kExprElse: {
blocks_.pop_back(); len = LookupTargetDelta(code, pc);
len = LookupTarget(code, pc);
TRACE(" end => @%zu\n", pc + len); TRACE(" end => @%zu\n", pc + len);
break; break;
} }
...@@ -1637,7 +1674,6 @@ class ThreadImpl { ...@@ -1637,7 +1674,6 @@ class ThreadImpl {
return DoTrap(kTrapUnreachable, pc); return DoTrap(kTrapUnreachable, pc);
} }
case kExprEnd: { case kExprEnd: {
blocks_.pop_back();
break; break;
} }
case kExprI32Const: { case kExprI32Const: {
...@@ -1965,6 +2001,12 @@ class ThreadImpl { ...@@ -1965,6 +2001,12 @@ class ThreadImpl {
UNREACHABLE(); UNREACHABLE();
} }
#ifdef DEBUG
if (!WasmOpcodes::IsControlOpcode(static_cast<WasmOpcode>(opcode))) {
DCHECK_EQ(expected_new_stack_height, stack_.size());
}
#endif
pc += len; pc += len;
if (pc == limit) { if (pc == limit) {
// Fell off end of code; do an implicit return. // Fell off end of code; do an implicit return.
...@@ -2485,8 +2527,16 @@ void WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function, ...@@ -2485,8 +2527,16 @@ void WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function,
} }
ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting( ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting(
Zone* zone, const byte* start, const byte* end) { Zone* zone, const WasmModule* module, const byte* start, const byte* end) {
ControlTransfers targets(zone, nullptr, start, 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_; return targets.map_;
} }
......
...@@ -21,16 +21,27 @@ namespace wasm { ...@@ -21,16 +21,27 @@ namespace wasm {
// forward declarations. // forward declarations.
struct ModuleBytesEnv; struct ModuleBytesEnv;
struct WasmFunction; struct WasmFunction;
struct WasmModule;
class WasmInterpreterInternals; class WasmInterpreterInternals;
typedef size_t pc_t; using pc_t = size_t;
typedef size_t sp_t; using sp_t = size_t;
typedef int32_t pcdiff_t; using pcdiff_t = int32_t;
typedef uint32_t spdiff_t; using spdiff_t = uint32_t;
const pc_t kInvalidPc = 0x80000000; constexpr pc_t kInvalidPc = 0x80000000;
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;
};
typedef ZoneMap<pc_t, pcdiff_t> ControlTransferMap; using ControlTransferMap = ZoneMap<pc_t, ControlTransferEntry>;
// Macro for defining union members. // Macro for defining union members.
#define FOREACH_UNION_MEMBER(V) \ #define FOREACH_UNION_MEMBER(V) \
...@@ -258,9 +269,8 @@ class V8_EXPORT_PRIVATE WasmInterpreter { ...@@ -258,9 +269,8 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// Computes the control transfers for the given bytecode. Used internally in // Computes the control transfers for the given bytecode. Used internally in
// the interpreter, but exposed for testing. // the interpreter, but exposed for testing.
static ControlTransferMap ComputeControlTransfersForTesting(Zone* zone, static ControlTransferMap ComputeControlTransfersForTesting(
const byte* start, Zone* zone, const WasmModule* module, const byte* start, const byte* end);
const byte* end);
private: private:
Zone zone_; Zone zone_;
......
...@@ -286,6 +286,17 @@ bool WasmOpcodes::IsPrefixOpcode(WasmOpcode opcode) { ...@@ -286,6 +286,17 @@ bool WasmOpcodes::IsPrefixOpcode(WasmOpcode opcode) {
return false; 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) { std::ostream& operator<<(std::ostream& os, const FunctionSig& sig) {
if (sig.return_count() == 0) os << "v"; if (sig.return_count() == 0) os << "v";
......
...@@ -597,6 +597,7 @@ class V8_EXPORT_PRIVATE WasmOpcodes { ...@@ -597,6 +597,7 @@ class V8_EXPORT_PRIVATE WasmOpcodes {
static FunctionSig* AsmjsSignature(WasmOpcode opcode); static FunctionSig* AsmjsSignature(WasmOpcode opcode);
static FunctionSig* AtomicSignature(WasmOpcode opcode); static FunctionSig* AtomicSignature(WasmOpcode opcode);
static bool IsPrefixOpcode(WasmOpcode opcode); static bool IsPrefixOpcode(WasmOpcode opcode);
static bool IsControlOpcode(WasmOpcode opcode);
static int TrapReasonToMessageId(TrapReason reason); static int TrapReasonToMessageId(TrapReason reason);
static const char* TrapReasonMessage(TrapReason reason); static const char* TrapReasonMessage(TrapReason reason);
......
...@@ -27,68 +27,75 @@ namespace wasm { ...@@ -27,68 +27,75 @@ namespace wasm {
#define TRANSFER_VOID 0 #define TRANSFER_VOID 0
#define TRANSFER_ONE 1 #define TRANSFER_ONE 1
struct ExpectedPcDelta { struct ExpectedControlTransfer {
pc_t pc; pc_t pc;
pcdiff_t expected; pcdiff_t pc_diff;
uint32_t sp_diff;
uint32_t target_arity;
}; };
// For nicer error messages. // For nicer error messages.
class ControlTransferMatcher : public MatcherInterface<const pcdiff_t&> { class ControlTransferMatcher
: public MatcherInterface<const ControlTransferEntry&> {
public: public:
explicit ControlTransferMatcher(pc_t pc, const pcdiff_t& expected) explicit ControlTransferMatcher(pc_t pc,
const ExpectedControlTransfer& expected)
: pc_(pc), expected_(expected) {} : pc_(pc), expected_(expected) {}
void DescribeTo(std::ostream* os) const override { 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 { MatchResultListener* listener) const override {
if (input != expected_) { if (input.pc_diff == expected_.pc_diff &&
*listener << "@" << pc_ << " pcdiff = " << input; input.sp_diff == expected_.sp_diff &&
return false; 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: private:
pc_t pc_; pc_t pc_;
const pcdiff_t& expected_; const ExpectedControlTransfer& expected_;
}; };
class ControlTransferTest : public TestWithZone { class ControlTransferTest : public TestWithZone {
public: public:
template <int code_len> template <int code_len>
void CheckPcDeltas(const byte (&code)[code_len], void CheckTransfers(
std::initializer_list<ExpectedPcDelta> expected_deltas) { 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 byte code_with_end[code_len + 1]; // NOLINT: code_len is a constant here
memcpy(code_with_end, code, code_len); memcpy(code_with_end, code, code_len);
code_with_end[code_len] = kExprEnd; code_with_end[code_len] = kExprEnd;
ControlTransferMap map = WasmInterpreter::ComputeControlTransfersForTesting( 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. // Check all control targets in the map.
for (auto& expected_delta : expected_deltas) { for (auto& expected_transfer : expected_transfers) {
pc_t pc = expected_delta.pc; pc_t pc = expected_transfer.pc;
auto it = map.find(pc); EXPECT_TRUE(map.count(pc) > 0) << "expected control target @" << pc;
if (it == map.end()) { if (!map.count(pc)) continue;
EXPECT_TRUE(false) << "expected control target @" << pc; auto& entry = map[pc];
} else { EXPECT_THAT(entry, MakeMatcher(new ControlTransferMatcher(
pcdiff_t expected = expected_delta.expected; pc, expected_transfer)));
pcdiff_t& target = it->second;
EXPECT_THAT(target,
MakeMatcher(new ControlTransferMatcher(pc, expected)));
}
} }
// Check there are no other control targets. // Check there are no other control targets.
CheckNoOtherTargets(code_with_end, code_with_end + code_len + 1, map, CheckNoOtherTargets(code_with_end, code_with_end + code_len + 1, map,
expected_deltas); expected_transfers);
} }
void CheckNoOtherTargets(const byte* start, const byte* end, void CheckNoOtherTargets(
ControlTransferMap& map, const byte* start, const byte* end, ControlTransferMap& map,
std::initializer_list<ExpectedPcDelta> targets) { std::initializer_list<ExpectedControlTransfer> targets) {
// Check there are no other control targets. // Check there are no other control targets.
for (pc_t pc = 0; start + pc < end; pc++) { for (pc_t pc = 0; start + pc < end; pc++) {
bool found = false; bool found = false;
...@@ -112,7 +119,7 @@ TEST_F(ControlTransferTest, SimpleIf) { ...@@ -112,7 +119,7 @@ TEST_F(ControlTransferTest, SimpleIf) {
kLocalVoid, // @3 kLocalVoid, // @3
kExprEnd // @4 kExprEnd // @4
}; };
CheckPcDeltas(code, {{2, 2}}); CheckTransfers(code, {{2, 2, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleIf1) { TEST_F(ControlTransferTest, SimpleIf1) {
...@@ -124,7 +131,7 @@ TEST_F(ControlTransferTest, SimpleIf1) { ...@@ -124,7 +131,7 @@ TEST_F(ControlTransferTest, SimpleIf1) {
kExprNop, // @4 kExprNop, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{2, 3}}); CheckTransfers(code, {{2, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleIf2) { TEST_F(ControlTransferTest, SimpleIf2) {
...@@ -137,7 +144,7 @@ TEST_F(ControlTransferTest, SimpleIf2) { ...@@ -137,7 +144,7 @@ TEST_F(ControlTransferTest, SimpleIf2) {
kExprNop, // @5 kExprNop, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{2, 4}}); CheckTransfers(code, {{2, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleIfElse) { TEST_F(ControlTransferTest, SimpleIfElse) {
...@@ -149,7 +156,7 @@ TEST_F(ControlTransferTest, SimpleIfElse) { ...@@ -149,7 +156,7 @@ TEST_F(ControlTransferTest, SimpleIfElse) {
kExprElse, // @4 kExprElse, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{2, 3}, {4, 2}}); CheckTransfers(code, {{2, 3, 0, 0}, {4, 2, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleIfElse_v1) { TEST_F(ControlTransferTest, SimpleIfElse_v1) {
...@@ -165,7 +172,7 @@ TEST_F(ControlTransferTest, SimpleIfElse_v1) { ...@@ -165,7 +172,7 @@ TEST_F(ControlTransferTest, SimpleIfElse_v1) {
0, // @8 0, // @8
kExprEnd // @9 kExprEnd // @9
}; };
CheckPcDeltas(code, {{2, 5}, {6, 4}}); CheckTransfers(code, {{2, 5, 0, 0}, {6, 4, 1, 0}});
} }
TEST_F(ControlTransferTest, SimpleIfElse1) { TEST_F(ControlTransferTest, SimpleIfElse1) {
...@@ -178,7 +185,7 @@ TEST_F(ControlTransferTest, SimpleIfElse1) { ...@@ -178,7 +185,7 @@ TEST_F(ControlTransferTest, SimpleIfElse1) {
kExprNop, // @5 kExprNop, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{2, 3}, {4, 3}}); CheckTransfers(code, {{2, 3, 0, 0}, {4, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, IfBr) { TEST_F(ControlTransferTest, IfBr) {
...@@ -191,7 +198,7 @@ TEST_F(ControlTransferTest, IfBr) { ...@@ -191,7 +198,7 @@ TEST_F(ControlTransferTest, IfBr) {
0, // @5 0, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{2, 4}, {4, 3}}); CheckTransfers(code, {{2, 4, 0, 0}, {4, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, IfBrElse) { TEST_F(ControlTransferTest, IfBrElse) {
...@@ -205,7 +212,7 @@ TEST_F(ControlTransferTest, IfBrElse) { ...@@ -205,7 +212,7 @@ TEST_F(ControlTransferTest, IfBrElse) {
kExprElse, // @6 kExprElse, // @6
kExprEnd // @7 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) { TEST_F(ControlTransferTest, IfElseBr) {
...@@ -219,15 +226,16 @@ TEST_F(ControlTransferTest, IfElseBr) { ...@@ -219,15 +226,16 @@ TEST_F(ControlTransferTest, IfElseBr) {
0, // @6 0, // @6
kExprEnd // @7 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) { TEST_F(ControlTransferTest, BlockEmpty) {
byte code[] = { byte code[] = {
kExprBlock, // @0 kExprBlock, // @0
kExprEnd // @1 kLocalVoid, // @1
kExprEnd // @2
}; };
CheckPcDeltas(code, {}); CheckTransfers(code, {});
} }
TEST_F(ControlTransferTest, Br0) { TEST_F(ControlTransferTest, Br0) {
...@@ -238,7 +246,7 @@ TEST_F(ControlTransferTest, Br0) { ...@@ -238,7 +246,7 @@ TEST_F(ControlTransferTest, Br0) {
0, // @3 0, // @3
kExprEnd // @4 kExprEnd // @4
}; };
CheckPcDeltas(code, {{2, 3}}); CheckTransfers(code, {{2, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, Br1) { TEST_F(ControlTransferTest, Br1) {
...@@ -250,7 +258,7 @@ TEST_F(ControlTransferTest, Br1) { ...@@ -250,7 +258,7 @@ TEST_F(ControlTransferTest, Br1) {
0, // @4 0, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{3, 3}}); CheckTransfers(code, {{3, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, Br_v1a) { TEST_F(ControlTransferTest, Br_v1a) {
...@@ -263,7 +271,7 @@ TEST_F(ControlTransferTest, Br_v1a) { ...@@ -263,7 +271,7 @@ TEST_F(ControlTransferTest, Br_v1a) {
0, // @5 0, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{4, 3}}); CheckTransfers(code, {{4, 3, 1, 0}});
} }
TEST_F(ControlTransferTest, Br_v1b) { TEST_F(ControlTransferTest, Br_v1b) {
...@@ -276,7 +284,7 @@ TEST_F(ControlTransferTest, Br_v1b) { ...@@ -276,7 +284,7 @@ TEST_F(ControlTransferTest, Br_v1b) {
0, // @5 0, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{4, 3}}); CheckTransfers(code, {{4, 3, 1, 0}});
} }
TEST_F(ControlTransferTest, Br_v1c) { TEST_F(ControlTransferTest, Br_v1c) {
...@@ -289,7 +297,20 @@ TEST_F(ControlTransferTest, Br_v1c) { ...@@ -289,7 +297,20 @@ TEST_F(ControlTransferTest, Br_v1c) {
0, // @5 0, // @5
kExprEnd // @6 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) { TEST_F(ControlTransferTest, Br2) {
...@@ -302,7 +323,7 @@ TEST_F(ControlTransferTest, Br2) { ...@@ -302,7 +323,7 @@ TEST_F(ControlTransferTest, Br2) {
0, // @5 0, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{4, 3}}); CheckTransfers(code, {{4, 3, 0, 0}});
} }
TEST_F(ControlTransferTest, Br0b) { TEST_F(ControlTransferTest, Br0b) {
...@@ -314,7 +335,7 @@ TEST_F(ControlTransferTest, Br0b) { ...@@ -314,7 +335,7 @@ TEST_F(ControlTransferTest, Br0b) {
kExprNop, // @4 kExprNop, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{2, 4}}); CheckTransfers(code, {{2, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, Br0c) { TEST_F(ControlTransferTest, Br0c) {
...@@ -327,7 +348,7 @@ TEST_F(ControlTransferTest, Br0c) { ...@@ -327,7 +348,7 @@ TEST_F(ControlTransferTest, Br0c) {
kExprNop, // @5 kExprNop, // @5
kExprEnd // @6 kExprEnd // @6
}; };
CheckPcDeltas(code, {{2, 5}}); CheckTransfers(code, {{2, 5, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleLoop1) { TEST_F(ControlTransferTest, SimpleLoop1) {
...@@ -338,7 +359,7 @@ TEST_F(ControlTransferTest, SimpleLoop1) { ...@@ -338,7 +359,7 @@ TEST_F(ControlTransferTest, SimpleLoop1) {
0, // @3 0, // @3
kExprEnd // @4 kExprEnd // @4
}; };
CheckPcDeltas(code, {{2, -2}}); CheckTransfers(code, {{2, -2, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleLoop2) { TEST_F(ControlTransferTest, SimpleLoop2) {
...@@ -350,7 +371,7 @@ TEST_F(ControlTransferTest, SimpleLoop2) { ...@@ -350,7 +371,7 @@ TEST_F(ControlTransferTest, SimpleLoop2) {
0, // @4 0, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{3, -3}}); CheckTransfers(code, {{3, -3, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleLoopExit1) { TEST_F(ControlTransferTest, SimpleLoopExit1) {
...@@ -361,7 +382,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit1) { ...@@ -361,7 +382,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit1) {
1, // @3 1, // @3
kExprEnd // @4 kExprEnd // @4
}; };
CheckPcDeltas(code, {{2, 4}}); CheckTransfers(code, {{2, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, SimpleLoopExit2) { TEST_F(ControlTransferTest, SimpleLoopExit2) {
...@@ -373,7 +394,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit2) { ...@@ -373,7 +394,7 @@ TEST_F(ControlTransferTest, SimpleLoopExit2) {
1, // @4 1, // @4
kExprEnd // @5 kExprEnd // @5
}; };
CheckPcDeltas(code, {{3, 4}}); CheckTransfers(code, {{3, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, BrTable0) { TEST_F(ControlTransferTest, BrTable0) {
...@@ -387,7 +408,7 @@ TEST_F(ControlTransferTest, BrTable0) { ...@@ -387,7 +408,7 @@ TEST_F(ControlTransferTest, BrTable0) {
U32V_1(0), // @6 U32V_1(0), // @6
kExprEnd // @7 kExprEnd // @7
}; };
CheckPcDeltas(code, {{4, 4}}); CheckTransfers(code, {{4, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, BrTable0_v1a) { TEST_F(ControlTransferTest, BrTable0_v1a) {
...@@ -403,7 +424,7 @@ TEST_F(ControlTransferTest, BrTable0_v1a) { ...@@ -403,7 +424,7 @@ TEST_F(ControlTransferTest, BrTable0_v1a) {
U32V_1(0), // @8 U32V_1(0), // @8
kExprEnd // @9 kExprEnd // @9
}; };
CheckPcDeltas(code, {{6, 4}}); CheckTransfers(code, {{6, 4, 1, 0}});
} }
TEST_F(ControlTransferTest, BrTable0_v1b) { TEST_F(ControlTransferTest, BrTable0_v1b) {
...@@ -419,7 +440,7 @@ TEST_F(ControlTransferTest, BrTable0_v1b) { ...@@ -419,7 +440,7 @@ TEST_F(ControlTransferTest, BrTable0_v1b) {
U32V_1(0), // @8 U32V_1(0), // @8
kExprEnd // @9 kExprEnd // @9
}; };
CheckPcDeltas(code, {{6, 4}}); CheckTransfers(code, {{6, 4, 1, 0}});
} }
TEST_F(ControlTransferTest, BrTable1) { TEST_F(ControlTransferTest, BrTable1) {
...@@ -434,7 +455,7 @@ TEST_F(ControlTransferTest, BrTable1) { ...@@ -434,7 +455,7 @@ TEST_F(ControlTransferTest, BrTable1) {
U32V_1(0), // @7 U32V_1(0), // @7
kExprEnd // @8 kExprEnd // @8
}; };
CheckPcDeltas(code, {{4, 5}, {5, 4}}); CheckTransfers(code, {{4, 5, 0, 0}, {5, 4, 0, 0}});
} }
TEST_F(ControlTransferTest, BrTable2) { TEST_F(ControlTransferTest, BrTable2) {
...@@ -453,7 +474,29 @@ TEST_F(ControlTransferTest, BrTable2) { ...@@ -453,7 +474,29 @@ TEST_F(ControlTransferTest, BrTable2) {
kExprEnd, // @11 kExprEnd, // @11
kExprEnd // @12 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 } // namespace wasm
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment