Index: src/wasm/ast-decoder.cc |
diff --git a/src/wasm/ast-decoder.cc b/src/wasm/ast-decoder.cc |
index d762f6e3af29e348b2e804ef5d9779b519e85681..d2c346a3f7f7992b7023ef774a75295d8056b2e9 100644 |
--- a/src/wasm/ast-decoder.cc |
+++ b/src/wasm/ast-decoder.cc |
@@ -36,6 +36,8 @@ namespace wasm { |
error("Invalid opcode (enable with --" #flag ")"); \ |
break; \ |
} |
+// TODO(titzer): this is only for intermediate migration. |
+#define IMPLICIT_FUNCTION_END 1 |
// An SsaEnv environment carries the current local variable renaming |
// as well as the current effect and control dependency in the TF graph. |
@@ -70,41 +72,68 @@ struct Value { |
struct Control; |
-// An entry on the control stack (i.e. if, block, loop, try). |
+struct MergeValues { |
+ uint32_t arity; |
+ union { |
+ Value* array; |
+ Value first; |
+ } vals; // Either multiple values or a single value. |
+ |
+ Value& first() { |
+ DCHECK_GT(arity, 0u); |
+ return arity == 1 ? vals.first : vals.array[0]; |
+ } |
+}; |
+ |
+// IncomingBranch is used by exception handling code for managing finally's. |
+struct IncomingBranch { |
+ int32_t token_value; |
+ Control* target; |
+ MergeValues merge; |
+}; |
+ |
+static Value* NO_VALUE = nullptr; |
+ |
+enum ControlKind { kControlIf, kControlBlock, kControlLoop, kControlTry }; |
+ |
+// An entry on the control stack (i.e. if, block, loop). |
struct Control { |
const byte* pc; |
+ ControlKind kind; |
int stack_depth; // stack height at the beginning of the construct. |
SsaEnv* end_env; // end environment for the construct. |
SsaEnv* false_env; // false environment (only for if). |
SsaEnv* catch_env; // catch environment (only for try). |
- TFNode* node; // result node for the construct. |
- LocalType type; // result type for the construct. |
- bool is_loop; // true if this is the inner label of a loop. |
- bool is_if() const { return *pc == kExprIf; } |
+ // Values merged into the end of this control construct. |
+ MergeValues merge; |
- bool is_try() const { return *pc == kExprTry; } |
+ inline bool is_if() const { return kind == kControlIf; } |
+ inline bool is_block() const { return kind == kControlBlock; } |
+ inline bool is_loop() const { return kind == kControlLoop; } |
+ inline bool is_try() const { return kind == kControlTry; } |
// Named constructors. |
static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env) { |
- return {pc, stack_depth, end_env, nullptr, |
- nullptr, nullptr, kAstEnd, false}; |
+ return {pc, kControlBlock, stack_depth, end_env, |
+ nullptr, nullptr, {0, {NO_VALUE}}}; |
} |
static Control If(const byte* pc, int stack_depth, SsaEnv* end_env, |
SsaEnv* false_env) { |
- return {pc, stack_depth, end_env, false_env, |
- nullptr, nullptr, kAstStmt, false}; |
+ return {pc, kControlIf, stack_depth, end_env, |
+ false_env, nullptr, {0, {NO_VALUE}}}; |
} |
static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env) { |
- return {pc, stack_depth, end_env, nullptr, nullptr, nullptr, kAstEnd, true}; |
+ return {pc, kControlLoop, stack_depth, end_env, |
+ nullptr, nullptr, {0, {NO_VALUE}}}; |
} |
static Control Try(const byte* pc, int stack_depth, SsaEnv* end_env, |
SsaEnv* catch_env) { |
- return {pc, stack_depth, end_env, nullptr, |
- catch_env, nullptr, kAstEnd, false}; |
+ return {pc, kControlTry, stack_depth, end_env, |
+ nullptr, catch_env, {0, {NO_VALUE}}}; |
} |
}; |
@@ -139,17 +168,18 @@ class WasmDecoder : public Decoder { |
} |
return true; |
} |
- error(pc, pc + 1, "invalid local index"); |
+ error(pc, pc + 1, "invalid local index: %u", operand.index); |
return false; |
} |
inline bool Validate(const byte* pc, GlobalIndexOperand& operand) { |
ModuleEnv* m = module_; |
if (m && m->module && operand.index < m->module->globals.size()) { |
- operand.type = m->module->globals[operand.index].type; |
+ operand.global = &m->module->globals[operand.index]; |
+ operand.type = operand.global->type; |
return true; |
} |
- error(pc, pc + 1, "invalid global index"); |
+ error(pc, pc + 1, "invalid global index: %u", operand.index); |
return false; |
} |
@@ -164,16 +194,9 @@ class WasmDecoder : public Decoder { |
inline bool Validate(const byte* pc, CallFunctionOperand& operand) { |
if (Complete(pc, operand)) { |
- uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); |
- if (operand.arity != expected) { |
- error(pc, pc + 1, |
- "arity mismatch in direct function call (expected %u, got %u)", |
- expected, operand.arity); |
- return false; |
- } |
return true; |
} |
- error(pc, pc + 1, "invalid function index"); |
+ error(pc, pc + 1, "invalid function index: %u", operand.index); |
return false; |
} |
@@ -188,162 +211,28 @@ class WasmDecoder : public Decoder { |
inline bool Validate(const byte* pc, CallIndirectOperand& operand) { |
if (Complete(pc, operand)) { |
- uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); |
- if (operand.arity != expected) { |
- error(pc, pc + 1, |
- "arity mismatch in indirect function call (expected %u, got %u)", |
- expected, operand.arity); |
- return false; |
- } |
return true; |
} |
- error(pc, pc + 1, "invalid signature index"); |
- return false; |
- } |
- |
- inline bool Complete(const byte* pc, CallImportOperand& operand) { |
- ModuleEnv* m = module_; |
- if (m && m->module && operand.index < m->module->import_table.size()) { |
- operand.sig = m->module->import_table[operand.index].sig; |
- return true; |
- } |
- return false; |
- } |
- |
- inline bool Validate(const byte* pc, CallImportOperand& operand) { |
- if (Complete(pc, operand)) { |
- uint32_t expected = static_cast<uint32_t>(operand.sig->parameter_count()); |
- if (operand.arity != expected) { |
- error(pc, pc + 1, "arity mismatch in import call (expected %u, got %u)", |
- expected, operand.arity); |
- return false; |
- } |
- return true; |
- } |
- error(pc, pc + 1, "invalid signature index"); |
+ error(pc, pc + 1, "invalid signature index: #%u", operand.index); |
return false; |
} |
inline bool Validate(const byte* pc, BreakDepthOperand& operand, |
ZoneVector<Control>& control) { |
- if (operand.arity > 1) { |
- error(pc, pc + 1, "invalid arity for br or br_if"); |
- return false; |
- } |
if (operand.depth < control.size()) { |
operand.target = &control[control.size() - operand.depth - 1]; |
return true; |
} |
- error(pc, pc + 1, "invalid break depth"); |
+ error(pc, pc + 1, "invalid break depth: %u", operand.depth); |
return false; |
} |
bool Validate(const byte* pc, BranchTableOperand& operand, |
size_t block_depth) { |
- if (operand.arity > 1) { |
- error(pc, pc + 1, "invalid arity for break"); |
- return false; |
- } |
- // Verify table. |
- for (uint32_t i = 0; i < operand.table_count + 1; ++i) { |
- uint32_t target = operand.read_entry(this, i); |
- if (target >= block_depth) { |
- error(operand.table + i * 2, "improper branch in br_table"); |
- return false; |
- } |
- } |
+ // TODO(titzer): add extra redundant validation for br_table here? |
return true; |
} |
- unsigned OpcodeArity(const byte* pc) { |
-#define DECLARE_ARITY(name, ...) \ |
- static const LocalType kTypes_##name[] = {__VA_ARGS__}; \ |
- static const int kArity_##name = \ |
- static_cast<int>(arraysize(kTypes_##name) - 1); |
- |
- FOREACH_SIGNATURE(DECLARE_ARITY); |
-#undef DECLARE_ARITY |
- |
- switch (static_cast<WasmOpcode>(*pc)) { |
- case kExprI8Const: |
- case kExprI32Const: |
- case kExprI64Const: |
- case kExprF64Const: |
- case kExprF32Const: |
- case kExprGetLocal: |
- case kExprGetGlobal: |
- case kExprNop: |
- case kExprUnreachable: |
- case kExprEnd: |
- case kExprBlock: |
- case kExprThrow: |
- case kExprTry: |
- case kExprLoop: |
- return 0; |
- |
- case kExprSetGlobal: |
- case kExprSetLocal: |
- case kExprElse: |
- case kExprCatch: |
- return 1; |
- |
- case kExprBr: { |
- BreakDepthOperand operand(this, pc); |
- return operand.arity; |
- } |
- case kExprBrIf: { |
- BreakDepthOperand operand(this, pc); |
- return 1 + operand.arity; |
- } |
- case kExprBrTable: { |
- BranchTableOperand operand(this, pc); |
- return 1 + operand.arity; |
- } |
- |
- case kExprIf: |
- return 1; |
- case kExprSelect: |
- return 3; |
- |
- case kExprCallFunction: { |
- CallFunctionOperand operand(this, pc); |
- return operand.arity; |
- } |
- case kExprCallIndirect: { |
- CallIndirectOperand operand(this, pc); |
- return 1 + operand.arity; |
- } |
- case kExprCallImport: { |
- CallImportOperand operand(this, pc); |
- return operand.arity; |
- } |
- case kExprReturn: { |
- ReturnArityOperand operand(this, pc); |
- return operand.arity; |
- } |
- |
-#define DECLARE_OPCODE_CASE(name, opcode, sig) \ |
- case kExpr##name: \ |
- return kArity_##sig; |
- |
- FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_MISC_MEM_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_SIMPLE_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_SIMPLE_MEM_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_ASMJS_COMPAT_OPCODE(DECLARE_OPCODE_CASE) |
- FOREACH_SIMD_0_OPERAND_OPCODE(DECLARE_OPCODE_CASE) |
-#undef DECLARE_OPCODE_CASE |
-#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
- FOREACH_SIMD_1_OPERAND_OPCODE(DECLARE_OPCODE_CASE) |
-#undef DECLARE_OPCODE_CASE |
- return 1; |
- default: |
- UNREACHABLE(); |
- return 0; |
- } |
- } |
- |
unsigned OpcodeLength(const byte* pc) { |
switch (static_cast<WasmOpcode>(*pc)) { |
#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
@@ -373,12 +262,17 @@ class WasmDecoder : public Decoder { |
CallIndirectOperand operand(this, pc); |
return 1 + operand.length; |
} |
- case kExprCallImport: { |
- CallImportOperand operand(this, pc); |
+ |
+ case kExprTry: |
+ case kExprIf: // fall thru |
+ case kExprLoop: |
+ case kExprBlock: { |
+ BlockTypeOperand operand(this, pc); |
return 1 + operand.length; |
} |
case kExprSetLocal: |
+ case kExprTeeLocal: |
case kExprGetLocal: |
case kExprCatch: { |
LocalIndexOperand operand(this, pc); |
@@ -386,7 +280,8 @@ class WasmDecoder : public Decoder { |
} |
case kExprBrTable: { |
BranchTableOperand operand(this, pc); |
- return 1 + operand.length; |
+ BranchTableIterator iterator(this, operand); |
+ return 1 + iterator.length(); |
} |
case kExprI32Const: { |
ImmI32Operand operand(this, pc); |
@@ -402,14 +297,6 @@ class WasmDecoder : public Decoder { |
return 5; |
case kExprF64Const: |
return 9; |
- case kExprReturn: { |
- ReturnArityOperand operand(this, pc); |
- return 1 + operand.length; |
- } |
-#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
- FOREACH_SIMD_0_OPERAND_OPCODE(DECLARE_OPCODE_CASE) { return 2; } |
- FOREACH_SIMD_1_OPERAND_OPCODE(DECLARE_OPCODE_CASE) { return 3; } |
-#undef DECLARE_OPCODE_CASE |
default: |
return 1; |
} |
@@ -427,7 +314,8 @@ class WasmFullDecoder : public WasmDecoder { |
base_(body.base), |
local_type_vec_(zone), |
stack_(zone), |
- control_(zone) { |
+ control_(zone), |
+ last_end_found_(false) { |
local_types_ = &local_type_vec_; |
} |
@@ -440,7 +328,7 @@ class WasmFullDecoder : public WasmDecoder { |
control_.clear(); |
if (end_ < pc_) { |
- error(pc_, "function body end < start"); |
+ error("function body end < start"); |
return false; |
} |
@@ -450,23 +338,55 @@ class WasmFullDecoder : public WasmDecoder { |
if (failed()) return TraceFailed(); |
+#if IMPLICIT_FUNCTION_END |
+ // With implicit end support (old style), the function block |
+ // remains on the stack. Other control blocks are an error. |
+ if (control_.size() > 1) { |
+ error(pc_, control_.back().pc, "unterminated control structure"); |
+ return TraceFailed(); |
+ } |
+ |
+ // Assume an implicit end to the function body block. |
+ if (control_.size() == 1) { |
+ Control* c = &control_.back(); |
+ if (ssa_env_->go()) { |
+ FallThruTo(c); |
+ } |
+ |
+ if (c->end_env->go()) { |
+ // Push the end values onto the stack. |
+ stack_.resize(c->stack_depth); |
+ if (c->merge.arity == 1) { |
+ stack_.push_back(c->merge.vals.first); |
+ } else { |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ stack_.push_back(c->merge.vals.array[i]); |
+ } |
+ } |
+ |
+ TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); |
+ SetEnv("function:end", c->end_env); |
+ DoReturn(); |
+ TRACE("\n"); |
+ } |
+ } |
+#else |
if (!control_.empty()) { |
error(pc_, control_.back().pc, "unterminated control structure"); |
return TraceFailed(); |
} |
- if (ssa_env_->go()) { |
- TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); |
- DoReturn(); |
- if (failed()) return TraceFailed(); |
- TRACE("\n"); |
+ if (!last_end_found_) { |
+ error("function body must end with \"end\" opcode."); |
+ return false; |
} |
+#endif |
if (FLAG_trace_wasm_decode_time) { |
double ms = decode_timer.Elapsed().InMillisecondsF(); |
- PrintF("wasm-decode ok (%0.3f ms)\n\n", ms); |
+ PrintF("wasm-decode %s (%0.3f ms)\n\n", ok() ? "ok" : "failed", ms); |
} else { |
- TRACE("wasm-decode ok\n\n"); |
+ TRACE("wasm-decode %s\n\n", ok() ? "ok" : "failed"); |
} |
return true; |
@@ -519,6 +439,7 @@ class WasmFullDecoder : public WasmDecoder { |
ZoneVector<LocalType> local_type_vec_; // types of local variables. |
ZoneVector<Value> stack_; // stack of values. |
ZoneVector<Control> control_; // stack of blocks, loops, and ifs. |
+ bool last_end_found_; |
inline bool build() { return builder_ && ssa_env_->go(); } |
@@ -639,6 +560,24 @@ class WasmFullDecoder : public WasmDecoder { |
reinterpret_cast<const void*>(limit_), baserel(pc_), |
static_cast<int>(limit_ - start_), builder_ ? "graph building" : ""); |
+ { |
+ // Set up initial function block. |
+ SsaEnv* break_env = ssa_env_; |
+ SetEnv("initial env", Steal(break_env)); |
+ PushBlock(break_env); |
+ Control* c = &control_.back(); |
+ c->merge.arity = static_cast<uint32_t>(sig_->return_count()); |
+ |
+ if (c->merge.arity == 1) { |
+ c->merge.vals.first = {pc_, nullptr, sig_->GetReturn(0)}; |
+ } else if (c->merge.arity > 1) { |
+ c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ c->merge.vals.array[i] = {pc_, nullptr, sig_->GetReturn(i)}; |
+ } |
+ } |
+ } |
+ |
if (pc_ >= limit_) return; // Nothing to do. |
while (true) { // decoding loop. |
@@ -656,13 +595,15 @@ class WasmFullDecoder : public WasmDecoder { |
// Complex bytecode. |
switch (opcode) { |
case kExprNop: |
- Push(kAstStmt, nullptr); |
break; |
case kExprBlock: { |
// The break environment is the outer environment. |
+ BlockTypeOperand operand(this, pc_); |
SsaEnv* break_env = ssa_env_; |
PushBlock(break_env); |
SetEnv("block:start", Steal(break_env)); |
+ SetBlockType(&control_.back(), operand); |
+ len = 1 + operand.length; |
break; |
} |
case kExprThrow: { |
@@ -673,11 +614,14 @@ class WasmFullDecoder : public WasmDecoder { |
} |
case kExprTry: { |
CHECK_PROTOTYPE_OPCODE(wasm_eh_prototype); |
+ BlockTypeOperand operand(this, pc_); |
SsaEnv* outer_env = ssa_env_; |
SsaEnv* try_env = Steal(outer_env); |
SsaEnv* catch_env = Split(try_env); |
PushTry(outer_env, catch_env); |
- SetEnv("try:start", try_env); |
+ SetEnv("try_catch:start", try_env); |
+ SetBlockType(&control_.back(), operand); |
+ len = 1 + operand.length; |
break; |
} |
case kExprCatch: { |
@@ -686,18 +630,18 @@ class WasmFullDecoder : public WasmDecoder { |
len = 1 + operand.length; |
if (control_.empty()) { |
- error(pc_, "catch does not match a any try"); |
+ error("catch does not match any try"); |
break; |
} |
Control* c = &control_.back(); |
if (!c->is_try()) { |
- error(pc_, "catch does not match a try"); |
+ error("catch does not match any try"); |
break; |
} |
if (c->catch_env == nullptr) { |
- error(pc_, "catch already present for try with catch"); |
+ error("catch already present for try with catch"); |
break; |
} |
@@ -716,23 +660,23 @@ class WasmFullDecoder : public WasmDecoder { |
} |
PopUpTo(c->stack_depth); |
- |
break; |
} |
case kExprLoop: { |
- // The break environment is the outer environment. |
- SsaEnv* break_env = ssa_env_; |
- PushBlock(break_env); |
- SsaEnv* finish_try_env = Steal(break_env); |
+ BlockTypeOperand operand(this, pc_); |
+ SsaEnv* finish_try_env = Steal(ssa_env_); |
// The continue environment is the inner environment. |
PrepareForLoop(pc_, finish_try_env); |
SetEnv("loop:start", Split(finish_try_env)); |
ssa_env_->SetNotMerged(); |
PushLoop(finish_try_env); |
+ SetBlockType(&control_.back(), operand); |
+ len = 1 + operand.length; |
break; |
} |
case kExprIf: { |
// Condition on top of stack. Split environments for branches. |
+ BlockTypeOperand operand(this, pc_); |
Value cond = Pop(0, kAstI32); |
TFNode* if_true = nullptr; |
TFNode* if_false = nullptr; |
@@ -744,11 +688,13 @@ class WasmFullDecoder : public WasmDecoder { |
true_env->control = if_true; |
PushIf(end_env, false_env); |
SetEnv("if:true", true_env); |
+ SetBlockType(&control_.back(), operand); |
+ len = 1 + operand.length; |
break; |
} |
case kExprElse: { |
if (control_.empty()) { |
- error(pc_, "else does not match any if"); |
+ error("else does not match any if"); |
break; |
} |
Control* c = &control_.back(); |
@@ -760,31 +706,38 @@ class WasmFullDecoder : public WasmDecoder { |
error(pc_, c->pc, "else already present for if"); |
break; |
} |
- Value val = PopUpTo(c->stack_depth); |
- MergeInto(c->end_env, &c->node, &c->type, val); |
+ FallThruTo(c); |
// Switch to environment for false branch. |
+ stack_.resize(c->stack_depth); |
SetEnv("if_else:false", c->false_env); |
c->false_env = nullptr; // record that an else is already seen |
break; |
} |
case kExprEnd: { |
if (control_.empty()) { |
- error(pc_, "end does not match any if, try, or block"); |
- break; |
+ error("end does not match any if, try, or block"); |
+ return; |
} |
const char* name = "block:end"; |
Control* c = &control_.back(); |
- Value val = PopUpTo(c->stack_depth); |
- if (c->is_loop) { |
- // Loops always push control in pairs. |
+ if (c->is_loop()) { |
+ // A loop just leaves the values on the stack. |
+ TypeCheckLoopFallThru(c); |
PopControl(); |
- c = &control_.back(); |
- name = "loop:end"; |
- } else if (c->is_if()) { |
+ SetEnv("loop:end", ssa_env_); |
+ break; |
+ } |
+ if (c->is_if()) { |
if (c->false_env != nullptr) { |
// End the true branch of a one-armed if. |
Goto(c->false_env, c->end_env); |
- val = {val.pc, nullptr, kAstStmt}; |
+ if (ssa_env_->go() && stack_.size() != c->stack_depth) { |
+ error("end of if expected empty stack"); |
+ stack_.resize(c->stack_depth); |
+ } |
+ if (c->merge.arity > 0) { |
+ error("non-void one-armed if"); |
+ } |
name = "if:merge"; |
} else { |
// End the false branch of a two-armed if. |
@@ -795,19 +748,39 @@ class WasmFullDecoder : public WasmDecoder { |
// validate that catch was seen. |
if (c->catch_env != nullptr) { |
- error(pc_, "missing catch in try"); |
+ error("missing catch in try"); |
break; |
} |
} |
- |
- if (ssa_env_->go()) { |
- // Adds a fallthrough edge to the next control block. |
- MergeInto(c->end_env, &c->node, &c->type, val); |
- } |
+ FallThruTo(c); |
SetEnv(name, c->end_env); |
+ |
+ // Push the end values onto the stack. |
stack_.resize(c->stack_depth); |
- Push(c->type, c->node); |
+ if (c->merge.arity == 1) { |
+ stack_.push_back(c->merge.vals.first); |
+ } else { |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ stack_.push_back(c->merge.vals.array[i]); |
+ } |
+ } |
+ |
PopControl(); |
+ |
+ if (control_.empty()) { |
+ // If the last (implicit) control was popped, check we are at end. |
+ if (pc_ + 1 != end_) { |
+ error(pc_, pc_ + 1, "trailing code after function end"); |
+ } |
+ last_end_found_ = true; |
+ if (ssa_env_->go()) { |
+ // The result of the block is the return value. |
+ TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); |
+ DoReturn(); |
+ TRACE("\n"); |
+ } |
+ return; |
+ } |
break; |
} |
case kExprSelect: { |
@@ -816,7 +789,7 @@ class WasmFullDecoder : public WasmDecoder { |
Value tval = Pop(); |
if (tval.type == kAstStmt || tval.type != fval.type) { |
if (tval.type != kAstEnd && fval.type != kAstEnd) { |
- error(pc_, "type mismatch in select"); |
+ error("type mismatch in select"); |
break; |
} |
} |
@@ -838,39 +811,33 @@ class WasmFullDecoder : public WasmDecoder { |
} |
case kExprBr: { |
BreakDepthOperand operand(this, pc_); |
- Value val = {pc_, nullptr, kAstStmt}; |
- if (operand.arity) val = Pop(); |
if (Validate(pc_, operand, control_)) { |
- BreakTo(operand.target, val); |
+ BreakTo(operand.depth); |
} |
len = 1 + operand.length; |
- Push(kAstEnd, nullptr); |
+ EndControl(); |
break; |
} |
case kExprBrIf: { |
BreakDepthOperand operand(this, pc_); |
- Value cond = Pop(operand.arity, kAstI32); |
- Value val = {pc_, nullptr, kAstStmt}; |
- if (operand.arity == 1) val = Pop(); |
+ Value cond = Pop(0, kAstI32); |
if (ok() && Validate(pc_, operand, control_)) { |
SsaEnv* fenv = ssa_env_; |
SsaEnv* tenv = Split(fenv); |
fenv->SetNotMerged(); |
BUILD(Branch, cond.node, &tenv->control, &fenv->control); |
ssa_env_ = tenv; |
- BreakTo(operand.target, val); |
+ BreakTo(operand.depth); |
ssa_env_ = fenv; |
} |
len = 1 + operand.length; |
- Push(kAstStmt, nullptr); |
break; |
} |
case kExprBrTable: { |
BranchTableOperand operand(this, pc_); |
+ BranchTableIterator iterator(this, operand); |
if (Validate(pc_, operand, control_.size())) { |
- Value key = Pop(operand.arity, kAstI32); |
- Value val = {pc_, nullptr, kAstStmt}; |
- if (operand.arity == 1) val = Pop(); |
+ Value key = Pop(0, kAstI32); |
if (failed()) break; |
SsaEnv* break_env = ssa_env_; |
@@ -880,42 +847,43 @@ class WasmFullDecoder : public WasmDecoder { |
SsaEnv* copy = Steal(break_env); |
ssa_env_ = copy; |
- for (uint32_t i = 0; i < operand.table_count + 1; ++i) { |
- uint16_t target = operand.read_entry(this, i); |
+ while (iterator.has_next()) { |
+ uint32_t i = iterator.cur_index(); |
+ const byte* pos = iterator.pc(); |
+ uint32_t target = iterator.next(); |
+ if (target >= control_.size()) { |
+ error(pos, "improper branch in br_table"); |
+ break; |
+ } |
ssa_env_ = Split(copy); |
ssa_env_->control = (i == operand.table_count) |
? BUILD(IfDefault, sw) |
: BUILD(IfValue, i, sw); |
- int depth = target; |
- Control* c = &control_[control_.size() - depth - 1]; |
- MergeInto(c->end_env, &c->node, &c->type, val); |
+ BreakTo(target); |
} |
} else { |
// Only a default target. Do the equivalent of br. |
- uint16_t target = operand.read_entry(this, 0); |
- int depth = target; |
- Control* c = &control_[control_.size() - depth - 1]; |
- MergeInto(c->end_env, &c->node, &c->type, val); |
+ const byte* pos = iterator.pc(); |
+ uint32_t target = iterator.next(); |
+ if (target >= control_.size()) { |
+ error(pos, "improper branch in br_table"); |
+ break; |
+ } |
+ BreakTo(target); |
} |
// br_table ends the control flow like br. |
ssa_env_ = break_env; |
- Push(kAstStmt, nullptr); |
} |
- len = 1 + operand.length; |
+ len = 1 + iterator.length(); |
break; |
} |
case kExprReturn: { |
- ReturnArityOperand operand(this, pc_); |
- if (operand.arity != sig_->return_count()) { |
- error(pc_, pc_ + 1, "arity mismatch in return"); |
- } |
DoReturn(); |
- len = 1 + operand.length; |
break; |
} |
case kExprUnreachable: { |
- Push(kAstEnd, BUILD(Unreachable, position())); |
- ssa_env_->Kill(SsaEnv::kControlEnd); |
+ BUILD(Unreachable, position()); |
+ EndControl(); |
break; |
} |
case kExprI8Const: { |
@@ -965,11 +933,24 @@ class WasmFullDecoder : public WasmDecoder { |
if (Validate(pc_, operand)) { |
Value val = Pop(0, local_type_vec_[operand.index]); |
if (ssa_env_->locals) ssa_env_->locals[operand.index] = val.node; |
+ } |
+ len = 1 + operand.length; |
+ break; |
+ } |
+ case kExprTeeLocal: { |
+ LocalIndexOperand operand(this, pc_); |
+ if (Validate(pc_, operand)) { |
+ Value val = Pop(0, local_type_vec_[operand.index]); |
+ if (ssa_env_->locals) ssa_env_->locals[operand.index] = val.node; |
Push(val.type, val.node); |
} |
len = 1 + operand.length; |
break; |
} |
+ case kExprDrop: { |
+ Pop(); |
+ break; |
+ } |
case kExprGetGlobal: { |
GlobalIndexOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
@@ -981,9 +962,13 @@ class WasmFullDecoder : public WasmDecoder { |
case kExprSetGlobal: { |
GlobalIndexOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
- Value val = Pop(0, operand.type); |
- BUILD(SetGlobal, operand.index, val.node); |
- Push(val.type, val.node); |
+ if (operand.global->mutability) { |
+ Value val = Pop(0, operand.type); |
+ BUILD(SetGlobal, operand.index, val.node); |
+ } else { |
+ error(pc_, pc_ + 1, "immutable global #%u cannot be assigned", |
+ operand.index); |
+ } |
} |
len = 1 + operand.length; |
break; |
@@ -1003,7 +988,6 @@ class WasmFullDecoder : public WasmDecoder { |
case kExprI32LoadMem: |
len = DecodeLoadMem(kAstI32, MachineType::Int32()); |
break; |
- |
case kExprI64LoadMem8S: |
len = DecodeLoadMem(kAstI64, MachineType::Int8()); |
break; |
@@ -1067,15 +1051,15 @@ class WasmFullDecoder : public WasmDecoder { |
} |
break; |
case kExprMemorySize: |
- Push(kAstI32, BUILD(MemSize, 0)); |
+ Push(kAstI32, BUILD(CurrentMemoryPages)); |
break; |
case kExprCallFunction: { |
CallFunctionOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
TFNode** buffer = PopArgs(operand.sig); |
- TFNode* call = |
+ TFNode** rets = |
BUILD(CallDirect, operand.index, buffer, position()); |
- Push(GetReturnType(operand.sig), call); |
+ PushReturns(operand.sig, rets); |
} |
len = 1 + operand.length; |
break; |
@@ -1083,23 +1067,12 @@ class WasmFullDecoder : public WasmDecoder { |
case kExprCallIndirect: { |
CallIndirectOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
- TFNode** buffer = PopArgs(operand.sig); |
Value index = Pop(0, kAstI32); |
+ TFNode** buffer = PopArgs(operand.sig); |
if (buffer) buffer[0] = index.node; |
- TFNode* call = |
+ TFNode** rets = |
BUILD(CallIndirect, operand.index, buffer, position()); |
- Push(GetReturnType(operand.sig), call); |
- } |
- len = 1 + operand.length; |
- break; |
- } |
- case kExprCallImport: { |
- CallImportOperand operand(this, pc_); |
- if (Validate(pc_, operand)) { |
- TFNode** buffer = PopArgs(operand.sig); |
- TFNode* call = |
- BUILD(CallImport, operand.index, buffer, position()); |
- Push(GetReturnType(operand.sig), call); |
+ PushReturns(operand.sig, rets); |
} |
len = 1 + operand.length; |
break; |
@@ -1114,9 +1087,9 @@ class WasmFullDecoder : public WasmDecoder { |
len += DecodeSimdOpcode(opcode); |
break; |
} |
- default: |
+ default: { |
// Deal with special asmjs opcodes. |
- if (module_->origin == kAsmJsOrigin) { |
+ if (module_ && module_->origin == kAsmJsOrigin) { |
sig = WasmOpcodes::AsmjsSignature(opcode); |
if (sig) { |
BuildSimpleOperator(opcode, sig); |
@@ -1125,8 +1098,9 @@ class WasmFullDecoder : public WasmDecoder { |
error("Invalid opcode"); |
return; |
} |
+ } |
} |
- } // end complex bytecode |
+ } |
#if DEBUG |
if (FLAG_trace_wasm_decoder) { |
@@ -1150,7 +1124,8 @@ class WasmFullDecoder : public WasmDecoder { |
PrintF("[%u]", operand.index); |
break; |
} |
- case kExprSetLocal: { |
+ case kExprSetLocal: // fallthru |
+ case kExprTeeLocal: { |
LocalIndexOperand operand(this, val.pc); |
PrintF("[%u]", operand.index); |
break; |
@@ -1169,7 +1144,21 @@ class WasmFullDecoder : public WasmDecoder { |
return; |
} |
} // end decode loop |
- } // end DecodeFunctionBody() |
+ } |
+ |
+ void EndControl() { ssa_env_->Kill(SsaEnv::kControlEnd); } |
+ |
+ void SetBlockType(Control* c, BlockTypeOperand& operand) { |
+ c->merge.arity = operand.arity; |
+ if (c->merge.arity == 1) { |
+ c->merge.vals.first = {pc_, nullptr, operand.read_entry(0)}; |
+ } else if (c->merge.arity > 1) { |
+ c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ c->merge.vals.array[i] = {pc_, nullptr, operand.read_entry(i)}; |
+ } |
+ } |
+ } |
TFNode** PopArgs(FunctionSig* sig) { |
if (build()) { |
@@ -1233,7 +1222,6 @@ class WasmFullDecoder : public WasmDecoder { |
Value index = Pop(0, kAstI32); |
BUILD(StoreMem, mem_type, index.node, operand.offset, operand.alignment, |
val.node, position()); |
- Push(type, val.node); |
return 1 + operand.length; |
} |
@@ -1262,7 +1250,7 @@ class WasmFullDecoder : public WasmDecoder { |
TFNode* node = BUILD(SimdOp, opcode, inputs); |
Push(GetReturnType(sig), node); |
} else { |
- error(pc_, pc_, "invalid simd opcode"); |
+ error("invalid simd opcode"); |
} |
} |
} |
@@ -1280,12 +1268,21 @@ class WasmFullDecoder : public WasmDecoder { |
if (buffer) buffer[i] = val.node; |
} |
- Push(kAstEnd, BUILD(Return, count, buffer)); |
- ssa_env_->Kill(SsaEnv::kControlEnd); |
+ BUILD(Return, count, buffer); |
+ EndControl(); |
} |
void Push(LocalType type, TFNode* node) { |
- stack_.push_back({pc_, node, type}); |
+ if (type != kAstStmt && type != kAstEnd) { |
+ stack_.push_back({pc_, node, type}); |
+ } |
+ } |
+ |
+ void PushReturns(FunctionSig* sig, TFNode** rets) { |
+ for (size_t i = 0; i < sig->return_count(); i++) { |
+ // When verifying only, then {rets} will be null, so push null. |
+ Push(sig->GetReturn(i), rets ? rets[i] : nullptr); |
+ } |
} |
const char* SafeOpcodeNameAt(const byte* pc) { |
@@ -1294,6 +1291,10 @@ class WasmFullDecoder : public WasmDecoder { |
} |
Value Pop(int index, LocalType expected) { |
+ if (!ssa_env_->go()) { |
+ // Unreachable code is essentially not typechecked. |
+ return {pc_, nullptr, expected}; |
+ } |
Value val = Pop(); |
if (val.type != expected) { |
if (val.type != kAstEnd) { |
@@ -1306,6 +1307,10 @@ class WasmFullDecoder : public WasmDecoder { |
} |
Value Pop() { |
+ if (!ssa_env_->go()) { |
+ // Unreachable code is essentially not typechecked. |
+ return {pc_, nullptr, kAstEnd}; |
+ } |
size_t limit = control_.empty() ? 0 : control_.back().stack_depth; |
if (stack_.size() <= limit) { |
Value val = {pc_, nullptr, kAstStmt}; |
@@ -1318,6 +1323,10 @@ class WasmFullDecoder : public WasmDecoder { |
} |
Value PopUpTo(int stack_depth) { |
+ if (!ssa_env_->go()) { |
+ // Unreachable code is essentially not typechecked. |
+ return {pc_, nullptr, kAstEnd}; |
+ } |
if (stack_depth == stack_.size()) { |
Value val = {pc_, nullptr, kAstStmt}; |
return val; |
@@ -1335,35 +1344,82 @@ class WasmFullDecoder : public WasmDecoder { |
int startrel(const byte* ptr) { return static_cast<int>(ptr - start_); } |
- void BreakTo(Control* block, const Value& val) { |
- if (block->is_loop) { |
+ void BreakTo(unsigned depth) { |
+ if (!ssa_env_->go()) return; |
+ Control* c = &control_[control_.size() - depth - 1]; |
+ if (c->is_loop()) { |
// This is the inner loop block, which does not have a value. |
- Goto(ssa_env_, block->end_env); |
+ Goto(ssa_env_, c->end_env); |
} else { |
- // Merge the value into the production for the block. |
- MergeInto(block->end_env, &block->node, &block->type, val); |
+ // Merge the value(s) into the end of the block. |
+ if (static_cast<size_t>(c->stack_depth + c->merge.arity) > |
+ stack_.size()) { |
+ error( |
+ pc_, pc_, |
+ "expected at least %d values on the stack for br to @%d, found %d", |
+ c->merge.arity, startrel(c->pc), |
+ static_cast<int>(stack_.size() - c->stack_depth)); |
+ return; |
+ } |
+ MergeValuesInto(c); |
} |
} |
- void MergeInto(SsaEnv* target, TFNode** node, LocalType* type, |
- const Value& val) { |
+ void FallThruTo(Control* c) { |
if (!ssa_env_->go()) return; |
- DCHECK_NE(kAstEnd, val.type); |
+ // Merge the value(s) into the end of the block. |
+ int arity = static_cast<int>(c->merge.arity); |
+ if (c->stack_depth + arity != stack_.size()) { |
+ error(pc_, pc_, "expected %d elements on the stack for fallthru to @%d", |
+ arity, startrel(c->pc)); |
+ return; |
+ } |
+ MergeValuesInto(c); |
+ } |
+ inline Value& GetMergeValueFromStack(Control* c, int i) { |
+ return stack_[stack_.size() - c->merge.arity + i]; |
+ } |
+ |
+ void TypeCheckLoopFallThru(Control* c) { |
+ if (!ssa_env_->go()) return; |
+ // Fallthru must match arity exactly. |
+ int arity = static_cast<int>(c->merge.arity); |
+ if (c->stack_depth + arity != stack_.size()) { |
+ error(pc_, pc_, "expected %d elements on the stack for fallthru to @%d", |
+ arity, startrel(c->pc)); |
+ return; |
+ } |
+ // Typecheck the values left on the stack. |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ Value& val = GetMergeValueFromStack(c, i); |
+ Value& old = |
+ c->merge.arity == 1 ? c->merge.vals.first : c->merge.vals.array[i]; |
+ if (val.type != old.type) { |
+ error(pc_, pc_, "type error in merge[%d] (expected %s, got %s)", i, |
+ WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ void MergeValuesInto(Control* c) { |
+ SsaEnv* target = c->end_env; |
bool first = target->state == SsaEnv::kUnreachable; |
Goto(ssa_env_, target); |
- if (first) { |
- // first merge to this environment; set the type and the node. |
- *type = val.type; |
- *node = val.node; |
- } else if (val.type == *type && val.type != kAstStmt) { |
- // merge with the existing value for this block. |
- *node = CreateOrMergeIntoPhi(*type, target->control, *node, val.node); |
- } else { |
- // types don't match, or block is already a stmt. |
- *type = kAstStmt; |
- *node = nullptr; |
+ for (unsigned i = 0; i < c->merge.arity; i++) { |
+ Value& val = GetMergeValueFromStack(c, i); |
+ Value& old = |
+ c->merge.arity == 1 ? c->merge.vals.first : c->merge.vals.array[i]; |
+ if (val.type != old.type) { |
+ error(pc_, pc_, "type error in merge[%d] (expected %s, got %s)", i, |
+ WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); |
+ return; |
+ } |
+ old.node = |
+ first ? val.node : CreateOrMergeIntoPhi(old.type, target->control, |
+ old.node, val.node); |
} |
} |
@@ -1592,10 +1648,11 @@ class WasmFullDecoder : public WasmDecoder { |
case kExprIf: |
case kExprBlock: |
case kExprTry: |
+ length = OpcodeLength(pc); |
depth++; |
- DCHECK_EQ(1, OpcodeLength(pc)); |
break; |
- case kExprSetLocal: { |
+ case kExprSetLocal: // fallthru |
+ case kExprTeeLocal: { |
LocalIndexOperand operand(this, pc); |
if (assigned->length() > 0 && |
operand.index < static_cast<uint32_t>(assigned->length())) { |
@@ -1688,11 +1745,6 @@ unsigned OpcodeLength(const byte* pc, const byte* end) { |
return decoder.OpcodeLength(pc); |
} |
-unsigned OpcodeArity(const byte* pc, const byte* end) { |
- WasmDecoder decoder(nullptr, nullptr, pc, end); |
- return decoder.OpcodeArity(pc); |
-} |
- |
void PrintAstForDebugging(const byte* start, const byte* end) { |
AccountingAllocator allocator; |
OFStream os(stdout); |
@@ -1758,66 +1810,57 @@ bool PrintAst(AccountingAllocator* allocator, const FunctionBody& body, |
} |
switch (opcode) { |
- case kExprIf: |
case kExprElse: |
+ os << " // @" << i.pc_offset(); |
+ control_depth++; |
+ break; |
case kExprLoop: |
+ case kExprIf: |
case kExprBlock: |
- case kExprTry: |
+ case kExprTry: { |
+ BlockTypeOperand operand(&i, i.pc()); |
os << " // @" << i.pc_offset(); |
+ for (unsigned i = 0; i < operand.arity; i++) { |
+ os << " " << WasmOpcodes::TypeName(operand.read_entry(i)); |
+ } |
control_depth++; |
break; |
+ } |
case kExprEnd: |
os << " // @" << i.pc_offset(); |
control_depth--; |
break; |
case kExprBr: { |
BreakDepthOperand operand(&i, i.pc()); |
- os << " // arity=" << operand.arity << " depth=" << operand.depth; |
+ os << " // depth=" << operand.depth; |
break; |
} |
case kExprBrIf: { |
BreakDepthOperand operand(&i, i.pc()); |
- os << " // arity=" << operand.arity << " depth" << operand.depth; |
+ os << " // depth=" << operand.depth; |
break; |
} |
case kExprBrTable: { |
BranchTableOperand operand(&i, i.pc()); |
- os << " // arity=" << operand.arity |
- << " entries=" << operand.table_count; |
+ os << " // entries=" << operand.table_count; |
break; |
} |
case kExprCallIndirect: { |
CallIndirectOperand operand(&i, i.pc()); |
+ os << " // sig #" << operand.index; |
if (decoder.Complete(i.pc(), operand)) { |
- os << " // sig #" << operand.index << ": " << *operand.sig; |
- } else { |
- os << " // arity=" << operand.arity << " sig #" << operand.index; |
- } |
- break; |
- } |
- case kExprCallImport: { |
- CallImportOperand operand(&i, i.pc()); |
- if (decoder.Complete(i.pc(), operand)) { |
- os << " // import #" << operand.index << ": " << *operand.sig; |
- } else { |
- os << " // arity=" << operand.arity << " import #" << operand.index; |
+ os << ": " << *operand.sig; |
} |
break; |
} |
case kExprCallFunction: { |
CallFunctionOperand operand(&i, i.pc()); |
+ os << " // function #" << operand.index; |
if (decoder.Complete(i.pc(), operand)) { |
- os << " // function #" << operand.index << ": " << *operand.sig; |
- } else { |
- os << " // arity=" << operand.arity << " function #" << operand.index; |
+ os << ": " << *operand.sig; |
} |
break; |
} |
- case kExprReturn: { |
- ReturnArityOperand operand(&i, i.pc()); |
- os << " // arity=" << operand.arity; |
- break; |
- } |
default: |
break; |
} |