Index: src/wasm/ast-decoder.cc |
diff --git a/src/wasm/ast-decoder.cc b/src/wasm/ast-decoder.cc |
index d2c346a3f7f7992b7023ef774a75295d8056b2e9..d762f6e3af29e348b2e804ef5d9779b519e85681 100644 |
--- a/src/wasm/ast-decoder.cc |
+++ b/src/wasm/ast-decoder.cc |
@@ -36,8 +36,6 @@ |
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. |
@@ -72,68 +70,41 @@ |
struct Control; |
-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). |
+// An entry on the control stack (i.e. if, block, loop, try). |
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). |
- |
- // Values merged into the end of this control construct. |
- MergeValues merge; |
- |
- 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; } |
+ 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; } |
+ |
+ bool is_try() const { return *pc == kExprTry; } |
// Named constructors. |
static Control Block(const byte* pc, int stack_depth, SsaEnv* end_env) { |
- return {pc, kControlBlock, stack_depth, end_env, |
- nullptr, nullptr, {0, {NO_VALUE}}}; |
+ return {pc, stack_depth, end_env, nullptr, |
+ nullptr, nullptr, kAstEnd, false}; |
} |
static Control If(const byte* pc, int stack_depth, SsaEnv* end_env, |
SsaEnv* false_env) { |
- return {pc, kControlIf, stack_depth, end_env, |
- false_env, nullptr, {0, {NO_VALUE}}}; |
+ return {pc, stack_depth, end_env, false_env, |
+ nullptr, nullptr, kAstStmt, false}; |
} |
static Control Loop(const byte* pc, int stack_depth, SsaEnv* end_env) { |
- return {pc, kControlLoop, stack_depth, end_env, |
- nullptr, nullptr, {0, {NO_VALUE}}}; |
+ return {pc, stack_depth, end_env, nullptr, nullptr, nullptr, kAstEnd, true}; |
} |
static Control Try(const byte* pc, int stack_depth, SsaEnv* end_env, |
SsaEnv* catch_env) { |
- return {pc, kControlTry, stack_depth, end_env, |
- nullptr, catch_env, {0, {NO_VALUE}}}; |
+ return {pc, stack_depth, end_env, nullptr, |
+ catch_env, nullptr, kAstEnd, false}; |
} |
}; |
@@ -168,18 +139,17 @@ |
} |
return true; |
} |
- error(pc, pc + 1, "invalid local index: %u", operand.index); |
+ error(pc, pc + 1, "invalid local 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.global = &m->module->globals[operand.index]; |
- operand.type = operand.global->type; |
+ operand.type = m->module->globals[operand.index].type; |
return true; |
} |
- error(pc, pc + 1, "invalid global index: %u", operand.index); |
+ error(pc, pc + 1, "invalid global index"); |
return false; |
} |
@@ -194,9 +164,16 @@ |
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: %u", operand.index); |
+ error(pc, pc + 1, "invalid function index"); |
return false; |
} |
@@ -211,26 +188,160 @@ |
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: #%u", operand.index); |
+ 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"); |
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: %u", operand.depth); |
+ error(pc, pc + 1, "invalid break depth"); |
return false; |
} |
bool Validate(const byte* pc, BranchTableOperand& operand, |
size_t block_depth) { |
- // TODO(titzer): add extra redundant validation for br_table here? |
+ 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; |
+ } |
+ } |
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) { |
@@ -262,17 +373,12 @@ |
CallIndirectOperand operand(this, pc); |
return 1 + operand.length; |
} |
- |
- case kExprTry: |
- case kExprIf: // fall thru |
- case kExprLoop: |
- case kExprBlock: { |
- BlockTypeOperand operand(this, pc); |
+ case kExprCallImport: { |
+ CallImportOperand operand(this, pc); |
return 1 + operand.length; |
} |
case kExprSetLocal: |
- case kExprTeeLocal: |
case kExprGetLocal: |
case kExprCatch: { |
LocalIndexOperand operand(this, pc); |
@@ -280,8 +386,7 @@ |
} |
case kExprBrTable: { |
BranchTableOperand operand(this, pc); |
- BranchTableIterator iterator(this, operand); |
- return 1 + iterator.length(); |
+ return 1 + operand.length; |
} |
case kExprI32Const: { |
ImmI32Operand operand(this, pc); |
@@ -297,6 +402,14 @@ |
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; |
} |
@@ -314,8 +427,7 @@ |
base_(body.base), |
local_type_vec_(zone), |
stack_(zone), |
- control_(zone), |
- last_end_found_(false) { |
+ control_(zone) { |
local_types_ = &local_type_vec_; |
} |
@@ -328,7 +440,7 @@ |
control_.clear(); |
if (end_ < pc_) { |
- error("function body end < start"); |
+ error(pc_, "function body end < start"); |
return false; |
} |
@@ -338,55 +450,23 @@ |
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 (!last_end_found_) { |
- error("function body must end with \"end\" opcode."); |
- return false; |
- } |
-#endif |
+ if (ssa_env_->go()) { |
+ TRACE(" @%-8d #xx:%-20s|", startrel(pc_), "ImplicitReturn"); |
+ DoReturn(); |
+ if (failed()) return TraceFailed(); |
+ TRACE("\n"); |
+ } |
if (FLAG_trace_wasm_decode_time) { |
double ms = decode_timer.Elapsed().InMillisecondsF(); |
- PrintF("wasm-decode %s (%0.3f ms)\n\n", ok() ? "ok" : "failed", ms); |
+ PrintF("wasm-decode ok (%0.3f ms)\n\n", ms); |
} else { |
- TRACE("wasm-decode %s\n\n", ok() ? "ok" : "failed"); |
+ TRACE("wasm-decode ok\n\n"); |
} |
return true; |
@@ -439,7 +519,6 @@ |
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(); } |
@@ -560,24 +639,6 @@ |
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. |
@@ -595,15 +656,13 @@ |
// 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: { |
@@ -614,14 +673,11 @@ |
} |
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_catch:start", try_env); |
- SetBlockType(&control_.back(), operand); |
- len = 1 + operand.length; |
+ SetEnv("try:start", try_env); |
break; |
} |
case kExprCatch: { |
@@ -630,18 +686,18 @@ |
len = 1 + operand.length; |
if (control_.empty()) { |
- error("catch does not match any try"); |
+ error(pc_, "catch does not match a any try"); |
break; |
} |
Control* c = &control_.back(); |
if (!c->is_try()) { |
- error("catch does not match any try"); |
+ error(pc_, "catch does not match a try"); |
break; |
} |
if (c->catch_env == nullptr) { |
- error("catch already present for try with catch"); |
+ error(pc_, "catch already present for try with catch"); |
break; |
} |
@@ -660,23 +716,23 @@ |
} |
PopUpTo(c->stack_depth); |
+ |
break; |
} |
case kExprLoop: { |
- BlockTypeOperand operand(this, pc_); |
- SsaEnv* finish_try_env = Steal(ssa_env_); |
+ // The break environment is the outer environment. |
+ SsaEnv* break_env = ssa_env_; |
+ PushBlock(break_env); |
+ SsaEnv* finish_try_env = Steal(break_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; |
@@ -688,13 +744,11 @@ |
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("else does not match any if"); |
+ error(pc_, "else does not match any if"); |
break; |
} |
Control* c = &control_.back(); |
@@ -706,38 +760,31 @@ |
error(pc_, c->pc, "else already present for if"); |
break; |
} |
- FallThruTo(c); |
+ Value val = PopUpTo(c->stack_depth); |
+ MergeInto(c->end_env, &c->node, &c->type, val); |
// 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("end does not match any if, try, or block"); |
- return; |
+ error(pc_, "end does not match any if, try, or block"); |
+ break; |
} |
const char* name = "block:end"; |
Control* c = &control_.back(); |
- if (c->is_loop()) { |
- // A loop just leaves the values on the stack. |
- TypeCheckLoopFallThru(c); |
+ Value val = PopUpTo(c->stack_depth); |
+ if (c->is_loop) { |
+ // Loops always push control in pairs. |
PopControl(); |
- SetEnv("loop:end", ssa_env_); |
- break; |
- } |
- if (c->is_if()) { |
+ c = &control_.back(); |
+ name = "loop:end"; |
+ } else 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); |
- 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"); |
- } |
+ val = {val.pc, nullptr, kAstStmt}; |
name = "if:merge"; |
} else { |
// End the false branch of a two-armed if. |
@@ -748,39 +795,19 @@ |
// validate that catch was seen. |
if (c->catch_env != nullptr) { |
- error("missing catch in try"); |
+ error(pc_, "missing catch in try"); |
break; |
} |
} |
- FallThruTo(c); |
+ |
+ if (ssa_env_->go()) { |
+ // Adds a fallthrough edge to the next control block. |
+ MergeInto(c->end_env, &c->node, &c->type, val); |
+ } |
SetEnv(name, c->end_env); |
- |
- // 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]); |
- } |
- } |
- |
+ Push(c->type, c->node); |
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: { |
@@ -789,7 +816,7 @@ |
Value tval = Pop(); |
if (tval.type == kAstStmt || tval.type != fval.type) { |
if (tval.type != kAstEnd && fval.type != kAstEnd) { |
- error("type mismatch in select"); |
+ error(pc_, "type mismatch in select"); |
break; |
} |
} |
@@ -811,33 +838,39 @@ |
} |
case kExprBr: { |
BreakDepthOperand operand(this, pc_); |
+ Value val = {pc_, nullptr, kAstStmt}; |
+ if (operand.arity) val = Pop(); |
if (Validate(pc_, operand, control_)) { |
- BreakTo(operand.depth); |
+ BreakTo(operand.target, val); |
} |
len = 1 + operand.length; |
- EndControl(); |
+ Push(kAstEnd, nullptr); |
break; |
} |
case kExprBrIf: { |
BreakDepthOperand operand(this, pc_); |
- Value cond = Pop(0, kAstI32); |
+ Value cond = Pop(operand.arity, kAstI32); |
+ Value val = {pc_, nullptr, kAstStmt}; |
+ if (operand.arity == 1) val = Pop(); |
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.depth); |
+ BreakTo(operand.target, val); |
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(0, kAstI32); |
+ Value key = Pop(operand.arity, kAstI32); |
+ Value val = {pc_, nullptr, kAstStmt}; |
+ if (operand.arity == 1) val = Pop(); |
if (failed()) break; |
SsaEnv* break_env = ssa_env_; |
@@ -847,43 +880,42 @@ |
SsaEnv* copy = Steal(break_env); |
ssa_env_ = copy; |
- 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; |
- } |
+ for (uint32_t i = 0; i < operand.table_count + 1; ++i) { |
+ uint16_t target = operand.read_entry(this, i); |
ssa_env_ = Split(copy); |
ssa_env_->control = (i == operand.table_count) |
? BUILD(IfDefault, sw) |
: BUILD(IfValue, i, sw); |
- BreakTo(target); |
+ int depth = target; |
+ Control* c = &control_[control_.size() - depth - 1]; |
+ MergeInto(c->end_env, &c->node, &c->type, val); |
} |
} else { |
// Only a default target. Do the equivalent of br. |
- const byte* pos = iterator.pc(); |
- uint32_t target = iterator.next(); |
- if (target >= control_.size()) { |
- error(pos, "improper branch in br_table"); |
- break; |
- } |
- BreakTo(target); |
+ 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); |
} |
// br_table ends the control flow like br. |
ssa_env_ = break_env; |
- } |
- len = 1 + iterator.length(); |
+ Push(kAstStmt, nullptr); |
+ } |
+ len = 1 + operand.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: { |
- BUILD(Unreachable, position()); |
- EndControl(); |
+ Push(kAstEnd, BUILD(Unreachable, position())); |
+ ssa_env_->Kill(SsaEnv::kControlEnd); |
break; |
} |
case kExprI8Const: { |
@@ -933,22 +965,9 @@ |
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 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: { |
@@ -962,13 +981,9 @@ |
case kExprSetGlobal: { |
GlobalIndexOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
- 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); |
- } |
+ Value val = Pop(0, operand.type); |
+ BUILD(SetGlobal, operand.index, val.node); |
+ Push(val.type, val.node); |
} |
len = 1 + operand.length; |
break; |
@@ -988,6 +1003,7 @@ |
case kExprI32LoadMem: |
len = DecodeLoadMem(kAstI32, MachineType::Int32()); |
break; |
+ |
case kExprI64LoadMem8S: |
len = DecodeLoadMem(kAstI64, MachineType::Int8()); |
break; |
@@ -1051,15 +1067,15 @@ |
} |
break; |
case kExprMemorySize: |
- Push(kAstI32, BUILD(CurrentMemoryPages)); |
+ Push(kAstI32, BUILD(MemSize, 0)); |
break; |
case kExprCallFunction: { |
CallFunctionOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
TFNode** buffer = PopArgs(operand.sig); |
- TFNode** rets = |
+ TFNode* call = |
BUILD(CallDirect, operand.index, buffer, position()); |
- PushReturns(operand.sig, rets); |
+ Push(GetReturnType(operand.sig), call); |
} |
len = 1 + operand.length; |
break; |
@@ -1067,12 +1083,23 @@ |
case kExprCallIndirect: { |
CallIndirectOperand operand(this, pc_); |
if (Validate(pc_, operand)) { |
+ TFNode** buffer = PopArgs(operand.sig); |
Value index = Pop(0, kAstI32); |
+ if (buffer) buffer[0] = index.node; |
+ TFNode* call = |
+ 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); |
- if (buffer) buffer[0] = index.node; |
- TFNode** rets = |
- BUILD(CallIndirect, operand.index, buffer, position()); |
- PushReturns(operand.sig, rets); |
+ TFNode* call = |
+ BUILD(CallImport, operand.index, buffer, position()); |
+ Push(GetReturnType(operand.sig), call); |
} |
len = 1 + operand.length; |
break; |
@@ -1087,9 +1114,9 @@ |
len += DecodeSimdOpcode(opcode); |
break; |
} |
- default: { |
+ default: |
// Deal with special asmjs opcodes. |
- if (module_ && module_->origin == kAsmJsOrigin) { |
+ if (module_->origin == kAsmJsOrigin) { |
sig = WasmOpcodes::AsmjsSignature(opcode); |
if (sig) { |
BuildSimpleOperator(opcode, sig); |
@@ -1098,9 +1125,8 @@ |
error("Invalid opcode"); |
return; |
} |
- } |
} |
- } |
+ } // end complex bytecode |
#if DEBUG |
if (FLAG_trace_wasm_decoder) { |
@@ -1124,8 +1150,7 @@ |
PrintF("[%u]", operand.index); |
break; |
} |
- case kExprSetLocal: // fallthru |
- case kExprTeeLocal: { |
+ case kExprSetLocal: { |
LocalIndexOperand operand(this, val.pc); |
PrintF("[%u]", operand.index); |
break; |
@@ -1144,21 +1169,7 @@ |
return; |
} |
} // end decode loop |
- } |
- |
- 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)}; |
- } |
- } |
- } |
+ } // end DecodeFunctionBody() |
TFNode** PopArgs(FunctionSig* sig) { |
if (build()) { |
@@ -1222,6 +1233,7 @@ |
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; |
} |
@@ -1250,7 +1262,7 @@ |
TFNode* node = BUILD(SimdOp, opcode, inputs); |
Push(GetReturnType(sig), node); |
} else { |
- error("invalid simd opcode"); |
+ error(pc_, pc_, "invalid simd opcode"); |
} |
} |
} |
@@ -1268,21 +1280,12 @@ |
if (buffer) buffer[i] = val.node; |
} |
- BUILD(Return, count, buffer); |
- EndControl(); |
+ Push(kAstEnd, BUILD(Return, count, buffer)); |
+ ssa_env_->Kill(SsaEnv::kControlEnd); |
} |
void Push(LocalType type, TFNode* node) { |
- 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); |
- } |
+ stack_.push_back({pc_, node, type}); |
} |
const char* SafeOpcodeNameAt(const byte* pc) { |
@@ -1291,10 +1294,6 @@ |
} |
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) { |
@@ -1307,10 +1306,6 @@ |
} |
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}; |
@@ -1323,10 +1318,6 @@ |
} |
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; |
@@ -1344,82 +1335,35 @@ |
int startrel(const byte* ptr) { return static_cast<int>(ptr - start_); } |
- void BreakTo(unsigned depth) { |
+ void BreakTo(Control* block, const Value& val) { |
+ if (block->is_loop) { |
+ // This is the inner loop block, which does not have a value. |
+ Goto(ssa_env_, block->end_env); |
+ } else { |
+ // Merge the value into the production for the block. |
+ MergeInto(block->end_env, &block->node, &block->type, val); |
+ } |
+ } |
+ |
+ void MergeInto(SsaEnv* target, TFNode** node, LocalType* type, |
+ const Value& val) { |
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_, c->end_env); |
- } else { |
- // 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 FallThruTo(Control* c) { |
- if (!ssa_env_->go()) return; |
- // 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; |
+ DCHECK_NE(kAstEnd, val.type); |
+ |
bool first = target->state == SsaEnv::kUnreachable; |
Goto(ssa_env_, target); |
- 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); |
+ 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; |
} |
} |
@@ -1648,11 +1592,10 @@ |
case kExprIf: |
case kExprBlock: |
case kExprTry: |
- length = OpcodeLength(pc); |
depth++; |
+ DCHECK_EQ(1, OpcodeLength(pc)); |
break; |
- case kExprSetLocal: // fallthru |
- case kExprTeeLocal: { |
+ case kExprSetLocal: { |
LocalIndexOperand operand(this, pc); |
if (assigned->length() > 0 && |
operand.index < static_cast<uint32_t>(assigned->length())) { |
@@ -1745,6 +1688,11 @@ |
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); |
@@ -1810,55 +1758,64 @@ |
} |
switch (opcode) { |
+ case kExprIf: |
case kExprElse: |
+ case kExprLoop: |
+ case kExprBlock: |
+ case kExprTry: |
os << " // @" << i.pc_offset(); |
control_depth++; |
break; |
- case kExprLoop: |
- case kExprIf: |
- case kExprBlock: |
- 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 << " // depth=" << operand.depth; |
+ os << " // arity=" << operand.arity << " depth=" << operand.depth; |
break; |
} |
case kExprBrIf: { |
BreakDepthOperand operand(&i, i.pc()); |
- os << " // depth=" << operand.depth; |
+ os << " // arity=" << operand.arity << " depth" << operand.depth; |
break; |
} |
case kExprBrTable: { |
BranchTableOperand operand(&i, i.pc()); |
- os << " // entries=" << operand.table_count; |
+ os << " // arity=" << operand.arity |
+ << " entries=" << operand.table_count; |
break; |
} |
case kExprCallIndirect: { |
CallIndirectOperand operand(&i, i.pc()); |
- os << " // sig #" << operand.index; |
if (decoder.Complete(i.pc(), operand)) { |
- os << ": " << *operand.sig; |
+ 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; |
+ } |
+ break; |
+ } |
case kExprCallFunction: { |
CallFunctionOperand operand(&i, i.pc()); |
- os << " // function #" << operand.index; |
if (decoder.Complete(i.pc(), operand)) { |
- os << ": " << *operand.sig; |
+ os << " // function #" << operand.index << ": " << *operand.sig; |
+ } else { |
+ os << " // arity=" << operand.arity << " function #" << operand.index; |
} |
+ break; |
+ } |
+ case kExprReturn: { |
+ ReturnArityOperand operand(&i, i.pc()); |
+ os << " // arity=" << operand.arity; |
break; |
} |
default: |