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 {
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,6 +694,21 @@ Handle<HeapObject> UnwrapWasmToJSWrapper(Isolate* isolate,
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.
// Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to
// be directly executed without the need to dynamically track blocks.
......@@ -701,38 +716,61 @@ class ControlTransfers : public ZoneObject {
public:
ControlTransferMap map_;
ControlTransfers(Zone* zone, BodyLocalDecls* locals, const byte* start,
const byte* end)
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.
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;
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.
void Bind(ControlTransferMap* map, const byte* start, const byte* pc) {
void Bind(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);
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;
}
}
};
......@@ -743,9 +781,9 @@ class ControlTransfers : public ZoneObject {
CLabel* end_label;
CLabel* else_label;
void Ref(ControlTransferMap* map, const byte* start,
const byte* from_pc) {
end_label->Ref(map, start, from_pc);
void Finish(ControlTransferMap* map, const byte* start) {
end_label->Finish(map, start);
if (else_label) else_label->Finish(map, start);
}
};
......@@ -754,54 +792,72 @@ class ControlTransfers : public ZoneObject {
// 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()) {
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();
TRACE("@%u: control %s\n", i.pc_offset(),
WasmOpcodes::OpcodeName(opcode));
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: {
TRACE("control @%u: Block\n", i.pc_offset());
CLabel* label = new (zone) CLabel(zone);
control_stack.push_back({i.pc(), label, nullptr});
break;
}
case kExprBlock:
case kExprLoop: {
TRACE("control @%u: Loop\n", i.pc_offset());
CLabel* label = new (zone) CLabel(zone);
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});
label->Bind(&map_, start, i.pc());
if (loop) label->Bind(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);
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(&map_, start, i.pc());
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(&map_, start, i.pc());
c->end_label->Ref(i.pc(), stack_height);
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;
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());
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);
// 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;
}
......@@ -809,14 +865,14 @@ class ControlTransfers : public ZoneObject {
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());
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->Ref(&map_, start, i.pc());
c->end_label->Ref(i.pc(), stack_height);
break;
}
case kExprBrTable: {
......@@ -828,7 +884,7 @@ class ControlTransfers : public ZoneObject {
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);
c->end_label->Ref(i.pc() + j, stack_height);
}
break;
}
......@@ -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);
if (result == map_.end()) {
V8_Fatal(__FILE__, __LINE__, "no control target for pc %zu", from);
}
DCHECK(result != map_.end());
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 {
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;
const pc_t kInvalidPc = 0x80000000;
using pc_t = size_t;
using sp_t = size_t;
using pcdiff_t = int32_t;
using spdiff_t = uint32_t;
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.
#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
......
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