| 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;
|
| }
|
|
|