/* * Copyright 2016 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm-binary-reader-interpreter.h" #include #include #include #include "wasm-allocator.h" #include "wasm-binary-reader.h" #include "wasm-interpreter.h" #include "wasm-writer.h" #define LOG 0 #if LOG #define LOGF(...) fprintf(stderr, __VA_ARGS__) #else #define LOGF(...) (void)0 #endif #define INVALID_FUNC_INDEX ((uint32_t)~0) #define CHECK_ALLOC_(ctx, cond) \ do { \ if (!(cond)) { \ print_error((ctx), "%s:%d: allocation failed", __FILE__, __LINE__); \ return WASM_ERROR; \ } \ } while (0) #define CHECK_ALLOC(ctx, e) CHECK_ALLOC_((ctx), WASM_SUCCEEDED(e)) #define CHECK_ALLOC_NULL(ctx, v) CHECK_ALLOC_((ctx), (v)) #define CHECK_ALLOC_NULL_STR(ctx, v) CHECK_ALLOC_((ctx), (v).start) #define CHECK_RESULT(expr) \ do { \ if (WASM_FAILED(expr)) \ return WASM_ERROR; \ } while (0) #define CHECK_DEPTH(ctx, depth) \ do { \ if ((depth) >= (ctx)->typecheck_label_stack.size) { \ print_error((ctx), "invalid depth: %d (max %" PRIzd ")", (depth), \ ((ctx)->typecheck_label_stack.size)); \ return WASM_ERROR; \ } \ } while (0) #define CHECK_LOCAL(ctx, local_index) \ do { \ uint32_t max_local_index = \ (ctx)->current_func->param_and_local_types.size; \ if ((local_index) >= max_local_index) { \ print_error((ctx), "invalid local_index: %d (max %d)", (local_index), \ max_local_index); \ return WASM_ERROR; \ } \ } while (0) #define WASM_TYPE_ANY WASM_NUM_TYPES static const char* s_type_names[] = { "void", "i32", "i64", "f32", "f64", "any", }; WASM_STATIC_ASSERT(WASM_ARRAY_SIZE(s_type_names) == WASM_NUM_TYPES + 1); /* TODO(binji): combine with the ones defined in wasm-check? */ #define V(rtype, type1, type2, mem_size, code, NAME, text) \ [code] = WASM_TYPE_##rtype, static WasmType s_opcode_rtype[] = {WASM_FOREACH_OPCODE(V)}; #undef V #define V(rtype, type1, type2, mem_size, code, NAME, text) \ [code] = WASM_TYPE_##type1, static WasmType s_opcode_type1[] = {WASM_FOREACH_OPCODE(V)}; #undef V #define V(rtype, type1, type2, mem_size, code, NAME, text) \ [code] = WASM_TYPE_##type2, static WasmType s_opcode_type2[] = {WASM_FOREACH_OPCODE(V)}; #undef V #define V(rtype, type1, type2, mem_size, code, NAME, text) [code] = text, static const char* s_opcode_name[] = { /* clang-format off */ WASM_FOREACH_OPCODE(V) [WASM_OPCODE_ALLOCA] = "alloca", [WASM_OPCODE_DISCARD] = "discard", [WASM_OPCODE_DISCARD_KEEP] = "discard_keep", /* clang-format on */ }; #undef V WASM_DEFINE_VECTOR(uint32, WasmUint32); WASM_DEFINE_VECTOR(uint32_vector, WasmUint32Vector); typedef enum WasmLabelType { WASM_LABEL_TYPE_BLOCK, WASM_LABEL_TYPE_LOOP, WASM_LABEL_TYPE_IF, WASM_LABEL_TYPE_ELSE, } WasmLabelType; static const char* s_label_type_name[] = { "block", "loop", "if", "else", }; /* used for the typecheck pass */ typedef struct WasmTypecheckLabel { WasmLabelType label_type; WasmType type; uint32_t expr_stack_size; uint32_t expr_index; /* the index of the starting op (block/loop/if) */ uint32_t last_expr_index; /* last index of the true branch in an if */ } WasmTypecheckLabel; WASM_DEFINE_VECTOR(typecheck_label, WasmTypecheckLabel); /* used for the emit pass */ typedef struct WasmEmitLabel { WasmLabelType label_type; uint32_t offset; /* branch location in the istream */ uint32_t fixup_offset; uint32_t value_stack_size; WasmBool has_value; } WasmEmitLabel; WASM_DEFINE_VECTOR(emit_label, WasmEmitLabel); typedef struct WasmExprNode { uint32_t index; WasmType type; } WasmExprNode; WASM_DEFINE_VECTOR(expr_node, WasmExprNode); typedef struct WasmInterpreterFunc { uint32_t sig_index; uint32_t offset; uint32_t local_decl_count; uint32_t local_count; WasmTypeVector param_and_local_types; } WasmInterpreterFunc; WASM_DEFINE_ARRAY(interpreter_func, WasmInterpreterFunc); typedef struct WasmContext { WasmAllocator* allocator; WasmBinaryReader* reader; WasmBinaryErrorHandler* error_handler; WasmAllocator* memory_allocator; WasmInterpreterModule* module; WasmInterpreterFuncArray funcs; WasmInterpreterFunc* current_func; WasmExprNodeVector expr_stack; WasmTypecheckLabelVector typecheck_label_stack; WasmEmitLabelVector emit_label_stack; WasmUint32VectorVector func_fixups; WasmUint32VectorVector depth_fixups; WasmUint32Vector discarded_exprs; /* bitset */ uint32_t value_stack_size; uint32_t depth; uint32_t expr_count; uint32_t start_func_index; WasmMemoryWriter istream_writer; uint32_t istream_offset; } WasmContext; static WasmTypecheckLabel* get_typecheck_label(WasmContext* ctx, uint32_t depth) { assert(depth < ctx->typecheck_label_stack.size); return &ctx->typecheck_label_stack.data[depth]; } static WasmTypecheckLabel* top_typecheck_label(WasmContext* ctx) { return get_typecheck_label(ctx, ctx->typecheck_label_stack.size - 1); } static uint32_t get_value_count(WasmType result_type) { return (result_type == WASM_TYPE_VOID || result_type == WASM_TYPE_ANY) ? 0 : 1; } static WasmBool is_expr_discarded(WasmContext* ctx, uint32_t expr_index) { uint32_t word_index = expr_index >> 5; if (word_index >= ctx->discarded_exprs.size) return WASM_FALSE; uint32_t bit_index = expr_index & 31; assert(word_index < ctx->discarded_exprs.size); return (ctx->discarded_exprs.data[word_index] & (1 << bit_index)) ? WASM_TRUE : WASM_FALSE; } static void on_error(uint32_t offset, const char* message, void* user_data); static void print_error(WasmContext* ctx, const char* format, ...) { WASM_SNPRINTF_ALLOCA(buffer, length, format); on_error(WASM_INVALID_OFFSET, buffer, ctx); } static WasmInterpreterFunc* get_func(WasmContext* ctx, uint32_t func_index) { assert(func_index < ctx->funcs.size); return &ctx->funcs.data[func_index]; } static WasmInterpreterImport* get_import(WasmContext* ctx, uint32_t import_index) { assert(import_index < ctx->module->imports.size); return &ctx->module->imports.data[import_index]; } static WasmInterpreterExport* get_export(WasmContext* ctx, uint32_t export_index) { assert(export_index < ctx->module->exports.size); return &ctx->module->exports.data[export_index]; } static WasmInterpreterFuncSignature* get_signature(WasmContext* ctx, uint32_t sig_index) { assert(sig_index < ctx->module->sigs.size); return &ctx->module->sigs.data[sig_index]; } static WasmInterpreterFuncSignature* get_func_signature( WasmContext* ctx, WasmInterpreterFunc* func) { return get_signature(ctx, func->sig_index); } static WasmType get_local_index_type(WasmInterpreterFunc* func, uint32_t local_index) { assert(local_index < func->param_and_local_types.size); return func->param_and_local_types.data[local_index]; } static uint32_t translate_depth(WasmContext* ctx, size_t size, uint32_t depth) { assert(depth < size); return size - 1 - depth; } static uint32_t translate_local_index(WasmContext* ctx, uint32_t local_index) { assert(local_index < ctx->value_stack_size); return ctx->value_stack_size - local_index; } void on_error(uint32_t offset, const char* message, void* user_data) { WasmContext* ctx = user_data; if (ctx->error_handler->on_error) { ctx->error_handler->on_error(offset, message, ctx->error_handler->user_data); } } static WasmResult on_memory_initial_size_pages(uint32_t pages, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterMemory* memory = &ctx->module->memory; memory->allocator = ctx->memory_allocator; memory->page_size = pages; memory->byte_size = pages * WASM_PAGE_SIZE; CHECK_ALLOC_NULL( ctx, memory->data = wasm_alloc_zero( ctx->memory_allocator, memory->byte_size, WASM_DEFAULT_ALIGN)); return WASM_OK; } static WasmResult on_data_segment(uint32_t index, uint32_t address, const void* src_data, uint32_t size, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterMemory* memory = &ctx->module->memory; uint8_t* dst_data = memory->data; memcpy(&dst_data[address], src_data, size); return WASM_OK; } static WasmResult on_signature_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; CHECK_ALLOC(ctx, wasm_new_interpreter_func_signature_array( ctx->allocator, &ctx->module->sigs, count)); return WASM_OK; } static WasmResult on_signature(uint32_t index, WasmType result_type, uint32_t param_count, WasmType* param_types, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFuncSignature* sig = get_signature(ctx, index); sig->result_type = result_type; CHECK_ALLOC( ctx, wasm_reserve_types(ctx->allocator, &sig->param_types, param_count)); sig->param_types.size = param_count; memcpy(sig->param_types.data, param_types, param_count * sizeof(WasmType)); return WASM_OK; } static WasmResult on_import_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; CHECK_ALLOC(ctx, wasm_new_interpreter_import_array( ctx->allocator, &ctx->module->imports, count)); return WASM_OK; } static WasmResult on_import(uint32_t index, uint32_t sig_index, WasmStringSlice module_name, WasmStringSlice function_name, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterImport* import = &ctx->module->imports.data[index]; CHECK_ALLOC_NULL_STR(ctx, import->module_name = wasm_dup_string_slice( ctx->allocator, module_name)); CHECK_ALLOC_NULL_STR(ctx, import->func_name = wasm_dup_string_slice( ctx->allocator, function_name)); assert(sig_index < ctx->module->sigs.size); import->sig_index = sig_index; return WASM_OK; } static WasmResult on_function_signatures_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; CHECK_ALLOC( ctx, wasm_new_interpreter_func_array(ctx->allocator, &ctx->funcs, count)); CHECK_ALLOC(ctx, wasm_resize_uint32_vector_vector(ctx->allocator, &ctx->func_fixups, count)); return WASM_OK; } static WasmResult on_function_signature(uint32_t index, uint32_t sig_index, void* user_data) { WasmContext* ctx = user_data; assert(sig_index < ctx->module->sigs.size); WasmInterpreterFunc* func = get_func(ctx, index); func->offset = WASM_INVALID_OFFSET; func->sig_index = sig_index; return WASM_OK; } static WasmResult on_function_bodies_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; assert(count == ctx->funcs.size); WASM_USE(ctx); return WASM_OK; } /* defined below so they can reference s_binary_reader{,_emit} */ static WasmResult begin_function_body_pass(uint32_t index, uint32_t pass, void* user_data); static WasmResult end_function_body_pass(uint32_t index, uint32_t pass, void* user_data); /* Code generation happens per function in two passes. First, the function is * type-checked, bottom up. While this is done, a bitset ("discarded_exprs") is * maintained. * * There are three different but related meanings for a bit being set in * |discarded_exprs|: * * 1) if the operator is a block, loop or if: the bit is set if the block does * not use its value. This is used when emitting a br to determine whether its * value should be discarded. * * 2) if the operator is br_if: the bit is set if the br_if has a value. This * is used to determine whether the br_if value needs to be discarded if the * branch is not taken. * * 3) otherwise: the bit is set if the expression is subsequently discarded * (e.g. a set_local whose value is not used.) * * The second pass is the emit pass, where instructions are written to an * instruction stream, to be executed by the stack machine interpreter. * * Two passes are needed because you can't determine whether a value is used in * one pass. It would be possible to generate code in one pass, but it would * use a very large stack for blocks with many discarded expressions. */ /*** typecheck pass ***********************************************************/ static WasmResult type_mismatch(WasmContext* ctx, WasmType expected_type, WasmType type, const char* desc) { print_error(ctx, "type mismatch in %s, expected %s but got %s.", desc, s_type_names[expected_type], s_type_names[type]); return WASM_ERROR; } static WasmResult check_type(WasmContext* ctx, WasmType expected_type, WasmType type, const char* desc) { if (expected_type == WASM_TYPE_ANY || type == WASM_TYPE_ANY || expected_type == WASM_TYPE_VOID) { return WASM_OK; } if (expected_type == type) return WASM_OK; return type_mismatch(ctx, expected_type, type, desc); } static void unify_type(WasmType* dest_type, WasmType type) { if (*dest_type == WASM_TYPE_ANY) *dest_type = type; else if (type != WASM_TYPE_ANY && *dest_type != type) *dest_type = WASM_TYPE_VOID; } static WasmResult unify_and_check_type(WasmContext* ctx, WasmType* dest_type, WasmType type, const char* desc) { unify_type(dest_type, type); return check_type(ctx, *dest_type, type, desc); } static uint32_t translate_typecheck_depth(WasmContext* ctx, uint32_t depth) { return translate_depth(ctx, ctx->typecheck_label_stack.size, depth); } static WasmResult push_typecheck_label(WasmContext* ctx, WasmLabelType label_type, WasmType type) { WasmTypecheckLabel* label = wasm_append_typecheck_label(ctx->allocator, &ctx->typecheck_label_stack); CHECK_ALLOC_NULL(ctx, label); label->label_type = label_type; label->type = type; label->expr_stack_size = ctx->expr_stack.size; label->expr_index = ctx->expr_count; LOGF(" : +depth %" PRIzd ":%s\n", ctx->typecheck_label_stack.size - 1, s_type_names[type]); return WASM_OK; } static void pop_typecheck_label(WasmContext* ctx) { LOGF(" : -depth %" PRIzd "\n", ctx->typecheck_label_stack.size - 1); assert(ctx->typecheck_label_stack.size > 0); ctx->typecheck_label_stack.size--; } static WasmResult push_expr(WasmContext* ctx, WasmType type, WasmOpcode opcode) { LOGF("%3" PRIzd ": push %s:%s (#%u)\n", ctx->expr_stack.size, s_opcode_name[opcode], s_type_names[type], ctx->expr_count); WasmExprNode* expr; CHECK_ALLOC_NULL( ctx, expr = wasm_append_expr_node(ctx->allocator, &ctx->expr_stack)); expr->index = ctx->expr_count; expr->type = type; ctx->expr_count++; return WASM_OK; } static WasmType pop_expr(WasmContext* ctx) { assert(ctx->expr_stack.size > 0); WasmExprNode* expr = &ctx->expr_stack.data[ctx->expr_stack.size - 1]; LOGF("%3" PRIzd ": pop %s (#%u)\n", ctx->expr_stack.size, s_type_names[expr->type], expr->index); WasmType type = expr->type; ctx->expr_stack.size--; return type; } static WasmResult set_expr_discarded(WasmContext* ctx, uint32_t expr_index) { LOGF(" : set_expr_discarded #%u\n", expr_index); uint32_t word_index = expr_index >> 5; size_t new_size = word_index + 1; if (new_size > ctx->discarded_exprs.capacity) { CHECK_RESULT( wasm_reserve_uint32s(ctx->allocator, &ctx->discarded_exprs, new_size)); } size_t old_size = ctx->discarded_exprs.size; if (new_size > old_size) { memset(&ctx->discarded_exprs.data[old_size], 0, (new_size - old_size) * sizeof(uint32_t)); ctx->discarded_exprs.size = new_size; } uint32_t bit_index = expr_index & 31; ctx->discarded_exprs.data[word_index] |= 1 << bit_index; return WASM_OK; } static WasmResult set_expr_discarded_unless_void(WasmContext* ctx, uint32_t expr_index, WasmType type) { if (type != WASM_TYPE_VOID) return set_expr_discarded(ctx, expr_index); return WASM_OK; } static WasmResult begin_function_body(uint32_t index, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFunc* func = get_func(ctx, index); WasmInterpreterFuncSignature* sig = get_signature(ctx, func->sig_index); ctx->current_func = func; ctx->expr_stack.size = 0; ctx->typecheck_label_stack.size = 0; ctx->discarded_exprs.size = 0; ctx->depth = 0; ctx->expr_count = 0; /* append param types */ uint32_t i; for (i = 0; i < sig->param_types.size; ++i) { CHECK_RESULT(wasm_append_type_value(ctx->allocator, &func->param_and_local_types, &sig->param_types.data[i])); } return WASM_OK; } static WasmResult end_function_body(uint32_t index, void* user_data) { WasmContext* ctx = user_data; /* discard everything except the last expr */ uint32_t discard_max = ctx->expr_stack.size - 1; WasmInterpreterFuncSignature* sig = get_func_signature(ctx, ctx->current_func); if (sig->result_type == WASM_TYPE_VOID) { /* discard last expr too */ discard_max++; } uint32_t i; for (i = 0; i < discard_max; ++i) { WasmExprNode* expr = &ctx->expr_stack.data[i]; set_expr_discarded_unless_void(ctx, expr->index, expr->type); } ctx->current_func = NULL; return WASM_OK; } static WasmResult on_local_decl(uint32_t decl_index, uint32_t count, WasmType type, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFunc* func = ctx->current_func; uint32_t i; for (i = 0; i < count; ++i) { CHECK_RESULT(wasm_append_type_value(ctx->allocator, &func->param_and_local_types, &type)); } return WASM_OK; } static WasmResult on_unary_expr(WasmOpcode opcode, void* user_data) { WasmContext* ctx = user_data; WasmType value = pop_expr(ctx); CHECK_RESULT( check_type(ctx, s_opcode_type1[opcode], value, s_opcode_name[opcode])); return push_expr(ctx, s_opcode_rtype[opcode], opcode); } static WasmResult on_binary_expr(WasmOpcode opcode, void* user_data) { WasmContext* ctx = user_data; WasmType right = pop_expr(ctx); WasmType left = pop_expr(ctx); /* TODO use opcode name here */ CHECK_RESULT( check_type(ctx, s_opcode_type1[opcode], left, s_opcode_name[opcode])); CHECK_RESULT( check_type(ctx, s_opcode_type2[opcode], right, s_opcode_name[opcode])); return push_expr(ctx, s_opcode_rtype[opcode], opcode); } static WasmResult on_block_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": block (#%u)\n", ctx->expr_stack.size, ctx->expr_count); CHECK_RESULT(push_typecheck_label(ctx, WASM_LABEL_TYPE_BLOCK, WASM_TYPE_ANY)); ctx->expr_count++; return WASM_OK; } static WasmResult on_loop_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": loop (#%u)\n", ctx->expr_stack.size, ctx->expr_count); /* exit */ CHECK_RESULT(push_typecheck_label(ctx, WASM_LABEL_TYPE_LOOP, WASM_TYPE_ANY)); /* continue */ CHECK_RESULT(push_typecheck_label(ctx, WASM_LABEL_TYPE_LOOP, WASM_TYPE_VOID)); ctx->expr_count++; return WASM_OK; } static WasmResult on_if_expr(void* user_data) { WasmContext* ctx = user_data; WasmType cond = pop_expr(ctx); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, cond, "if")); LOGF("%3" PRIzd ": if (#%u)\n", ctx->expr_stack.size, ctx->expr_count); CHECK_RESULT(push_typecheck_label(ctx, WASM_LABEL_TYPE_IF, WASM_TYPE_ANY)); ctx->expr_count++; return WASM_OK; } static WasmResult on_else_expr(void* user_data) { WasmContext* ctx = user_data; WasmTypecheckLabel* label = top_typecheck_label(ctx); if (!label || label->label_type != WASM_LABEL_TYPE_IF) { print_error(ctx, "unexpected else operator"); return WASM_ERROR; } LOGF("%3" PRIzd ": else (#%u)\n", ctx->expr_stack.size, ctx->expr_count); if (label->expr_stack_size < ctx->expr_stack.size) { WasmExprNode* last_expr = &ctx->expr_stack.data[ctx->expr_stack.size - 1]; label->last_expr_index = last_expr->index; CHECK_RESULT(unify_and_check_type(ctx, &label->type, last_expr->type, "if true branch")); /* discard everything except the last expr; we don't know if we'll need to * discard that one until after we process the false branch */ uint32_t i; for (i = label->expr_stack_size; i < ctx->expr_stack.size - 1; ++i) { WasmExprNode* expr = &ctx->expr_stack.data[i]; set_expr_discarded_unless_void(ctx, expr->index, expr->type); } } label->label_type = WASM_LABEL_TYPE_ELSE; ctx->expr_stack.size = label->expr_stack_size; return WASM_OK; } static WasmResult on_end_expr(void* user_data) { WasmContext* ctx = user_data; WasmTypecheckLabel* label = top_typecheck_label(ctx); if (!label) { print_error(ctx, "unexpected end operator"); return WASM_ERROR; } if (label->label_type == WASM_LABEL_TYPE_LOOP) { /* pop the "continue" label now; any type checking we do below only applies * to the exit label */ pop_typecheck_label(ctx); label = top_typecheck_label(ctx); assert(label->label_type == WASM_LABEL_TYPE_LOOP); } else if (label->label_type == WASM_LABEL_TYPE_IF) { label->type = WASM_TYPE_VOID; } if (label->expr_stack_size < ctx->expr_stack.size) { WasmExprNode* last_expr = &ctx->expr_stack.data[ctx->expr_stack.size - 1]; WasmType old_type = label->type; CHECK_RESULT(unify_and_check_type(ctx, &label->type, last_expr->type, s_label_type_name[label->type])); if (label->label_type == WASM_LABEL_TYPE_ELSE && get_value_count(old_type) > get_value_count(label->type)) { /* we unified the types between the true and false branches; the true * branch had a value and the false branch didn't, so we need to discard * the true branch's value */ set_expr_discarded(ctx, label->last_expr_index); } /* discard everything except the last expr */ uint32_t i; for (i = label->expr_stack_size; i < ctx->expr_stack.size - 1; ++i) { WasmExprNode* expr = &ctx->expr_stack.data[i]; set_expr_discarded_unless_void(ctx, expr->index, expr->type); } if (label->type == WASM_TYPE_VOID) { /* discard the last child expr if this block/if/loop is void */ set_expr_discarded_unless_void(ctx, last_expr->index, last_expr->type); } } else { /* the block/loop/if was empty */ label->type = WASM_TYPE_VOID; } if (label->type == WASM_TYPE_VOID) { /* the "discarded" bit is overloaded for the initial block/loop/if * operators; in that case it means that there is no value expected to be * returned, so if a br operator yields a value, it should be immediately * discarded */ set_expr_discarded(ctx, label->expr_index); } ctx->expr_stack.size = label->expr_stack_size; pop_typecheck_label(ctx); return push_expr(ctx, label->type, WASM_OPCODE_END); } static WasmResult on_br_expr(uint32_t depth, void* user_data) { WasmContext* ctx = user_data; WasmType value = pop_expr(ctx); CHECK_DEPTH(ctx, depth); depth = translate_typecheck_depth(ctx, depth); WasmTypecheckLabel* label = get_typecheck_label(ctx, depth); CHECK_RESULT(unify_and_check_type(ctx, &label->type, value, "br")); return push_expr(ctx, WASM_TYPE_ANY, WASM_OPCODE_BR); } static WasmResult on_br_if_expr(uint32_t depth, void* user_data) { WasmContext* ctx = user_data; WasmType cond = pop_expr(ctx); WasmType value = pop_expr(ctx); CHECK_DEPTH(ctx, depth); depth = translate_typecheck_depth(ctx, depth); WasmTypecheckLabel* label = get_typecheck_label(ctx, depth); CHECK_RESULT(unify_and_check_type(ctx, &label->type, value, "br_if")); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, cond, "br_if")); /* the "discarded" bit is overloaded for br_if; if set, the br_if value is * non-void; i.e. should be discarded if the branch is not taken */ set_expr_discarded_unless_void(ctx, ctx->expr_count, value); return push_expr(ctx, WASM_TYPE_VOID, WASM_OPCODE_BR_IF); } static WasmResult on_br_table_expr(uint32_t num_targets, uint32_t* target_depths, uint32_t default_target_depth, void* user_data) { WasmContext* ctx = user_data; WasmType key = pop_expr(ctx); WasmType value = pop_expr(ctx); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, key, "br_table")); uint32_t i; for (i = 0; i <= num_targets; ++i) { uint32_t depth = i != num_targets ? target_depths[i] : default_target_depth; uint32_t translated_depth = translate_typecheck_depth(ctx, depth); WasmTypecheckLabel* label = get_typecheck_label(ctx, translated_depth); CHECK_RESULT(unify_and_check_type(ctx, &label->type, value, "br_table")); } return push_expr(ctx, WASM_TYPE_ANY, WASM_OPCODE_BR_TABLE); } static WasmResult on_call_expr(uint32_t func_index, void* user_data) { WasmContext* ctx = user_data; assert(func_index < ctx->funcs.size); WasmInterpreterFunc* func = get_func(ctx, func_index); WasmInterpreterFuncSignature* sig = get_func_signature(ctx, func); uint32_t i; for (i = sig->param_types.size; i > 0; --i) { WasmType arg = pop_expr(ctx); CHECK_RESULT( check_type(ctx, sig->param_types.data[i - 1], arg, "call")); } return push_expr(ctx, sig->result_type, WASM_OPCODE_CALL_FUNCTION); } static WasmResult on_call_import_expr(uint32_t import_index, void* user_data) { WasmContext* ctx = user_data; assert(import_index < ctx->module->imports.size); WasmInterpreterImport* import = get_import(ctx, import_index); WasmInterpreterFuncSignature* sig = get_signature(ctx, import->sig_index); uint32_t i; for (i = sig->param_types.size; i > 0; --i) { WasmType arg = pop_expr(ctx); CHECK_RESULT(check_type(ctx, sig->param_types.data[i - 1], arg, "call_import")); } return push_expr(ctx, sig->result_type, WASM_OPCODE_CALL_IMPORT); } static WasmResult on_call_indirect_expr(uint32_t sig_index, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFuncSignature* sig = get_signature(ctx, sig_index); uint32_t i; for (i = sig->param_types.size; i > 0; --i) { WasmType arg = pop_expr(ctx); CHECK_RESULT(check_type(ctx, sig->param_types.data[i - 1], arg, "call_indirect")); } WasmType entry_index = pop_expr(ctx); CHECK_RESULT( check_type(ctx, WASM_TYPE_I32, entry_index, "call_indirect")); return push_expr(ctx, sig->result_type, WASM_OPCODE_CALL_INDIRECT); } static WasmResult on_i32_const_expr(uint32_t value, void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_I32, WASM_OPCODE_I32_CONST); } static WasmResult on_i64_const_expr(uint64_t value, void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_I64, WASM_OPCODE_I64_CONST); } static WasmResult on_f32_const_expr(uint32_t value_bits, void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_F32, WASM_OPCODE_F32_CONST); } static WasmResult on_f64_const_expr(uint64_t value_bits, void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_F64, WASM_OPCODE_F64_CONST); } static WasmResult on_get_local_expr(uint32_t local_index, void* user_data) { WasmContext* ctx = user_data; CHECK_LOCAL(ctx, local_index); WasmType type = get_local_index_type(ctx->current_func, local_index); return push_expr(ctx, type, WASM_OPCODE_GET_LOCAL); } static WasmResult on_set_local_expr(uint32_t local_index, void* user_data) { WasmContext* ctx = user_data; CHECK_LOCAL(ctx, local_index); WasmType type = get_local_index_type(ctx->current_func, local_index); WasmType value = pop_expr(ctx); CHECK_RESULT(check_type(ctx, type, value, "set_local")); return push_expr(ctx, type, WASM_OPCODE_SET_LOCAL); } static WasmResult on_grow_memory_expr(void* user_data) { WasmContext* ctx = user_data; WasmType value = pop_expr(ctx); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, value, "grow_memory")); return push_expr(ctx, WASM_TYPE_I32, WASM_OPCODE_GROW_MEMORY); } static WasmResult on_load_expr(WasmOpcode opcode, uint32_t alignment_log2, uint32_t offset, void* user_data) { WasmContext* ctx = user_data; WasmType addr = pop_expr(ctx); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, addr, s_opcode_name[opcode])); return push_expr(ctx, s_opcode_rtype[opcode], opcode); } static WasmResult on_store_expr(WasmOpcode opcode, uint32_t alignment_log2, uint32_t offset, void* user_data) { WasmContext* ctx = user_data; WasmType value = pop_expr(ctx); WasmType addr = pop_expr(ctx); WasmType type = s_opcode_rtype[opcode]; CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, addr, s_opcode_name[opcode])); CHECK_RESULT(check_type(ctx, type, value, s_opcode_name[opcode])); return push_expr(ctx, type, opcode); } static WasmResult on_memory_size_expr(void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_I32, WASM_OPCODE_MEMORY_SIZE); } static WasmResult on_nop_expr(void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_VOID, WASM_OPCODE_NOP); } static WasmResult on_return_expr(void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFuncSignature* sig = get_func_signature(ctx, ctx->current_func); if (get_value_count(sig->result_type)) { WasmType value = pop_expr(ctx); CHECK_RESULT(check_type(ctx, sig->result_type, value, "return")); } return push_expr(ctx, sig->result_type, WASM_OPCODE_RETURN); } static WasmResult on_select_expr(void* user_data) { WasmContext* ctx = user_data; WasmType cond = pop_expr(ctx); WasmType right = pop_expr(ctx); WasmType left = pop_expr(ctx); WasmType type = WASM_TYPE_ANY; CHECK_RESULT(unify_and_check_type(ctx, &type, left, "select")); CHECK_RESULT(unify_and_check_type(ctx, &type, right, "select")); CHECK_RESULT(check_type(ctx, WASM_TYPE_I32, cond, "select")); return push_expr(ctx, type, WASM_OPCODE_SELECT); } static WasmResult on_unreachable_expr(void* user_data) { WasmContext* ctx = user_data; return push_expr(ctx, WASM_TYPE_ANY, WASM_OPCODE_UNREACHABLE); } /*** emit pass ****************************************************************/ static uint32_t get_istream_offset(WasmContext* ctx) { return ctx->istream_offset; } static WasmResult emit_data_at(WasmContext* ctx, size_t offset, const void* data, size_t size) { return ctx->istream_writer.base.write_data( offset, data, size, ctx->istream_writer.base.user_data); } static WasmResult emit_data(WasmContext* ctx, const void* data, size_t size) { CHECK_RESULT(emit_data_at(ctx, ctx->istream_offset, data, size)); ctx->istream_offset += size; return WASM_OK; } static WasmResult emit_opcode(WasmContext* ctx, WasmOpcode opcode) { return emit_data(ctx, &opcode, sizeof(uint8_t)); } static WasmResult emit_i8(WasmContext* ctx, uint8_t value) { return emit_data(ctx, &value, sizeof(value)); } static WasmResult emit_i32(WasmContext* ctx, uint32_t value) { return emit_data(ctx, &value, sizeof(value)); } static WasmResult emit_i64(WasmContext* ctx, uint64_t value) { return emit_data(ctx, &value, sizeof(value)); } static WasmResult emit_i32_at(WasmContext* ctx, uint32_t offset, uint32_t value) { return emit_data_at(ctx, offset, &value, sizeof(value)); } static void adjust_value_stack(WasmContext* ctx, int32_t amount) { uint32_t old_size = ctx->value_stack_size; uint32_t new_size = old_size + (uint32_t)amount; assert((amount <= 0 && new_size <= old_size) || (amount > 0 && new_size > old_size)); WASM_USE(old_size); WASM_USE(new_size); ctx->value_stack_size += (uint32_t)amount; #ifndef NDEBUG if (ctx->emit_label_stack.size > 0) { assert(ctx->value_stack_size >= ctx->emit_label_stack.data[ctx->emit_label_stack.size - 1] .value_stack_size); } else { assert(ctx->value_stack_size >= ctx->current_func->param_and_local_types.size); } #endif } static WasmEmitLabel* get_emit_label(WasmContext* ctx, uint32_t depth) { assert(depth < ctx->emit_label_stack.size); return &ctx->emit_label_stack.data[depth]; } static WasmEmitLabel* top_emit_label(WasmContext* ctx) { return get_emit_label(ctx, ctx->emit_label_stack.size - 1); } static uint32_t translate_emit_depth(WasmContext* ctx, uint32_t depth) { return translate_depth(ctx, ctx->emit_label_stack.size, depth); } static WasmResult push_emit_label(WasmContext* ctx, WasmLabelType label_type, uint32_t offset, uint32_t fixup_offset, WasmBool has_value) { WasmEmitLabel* label = wasm_append_emit_label(ctx->allocator, &ctx->emit_label_stack); CHECK_ALLOC_NULL(ctx, label); label->label_type = label_type; label->offset = offset; label->value_stack_size = ctx->value_stack_size; label->has_value = has_value; label->fixup_offset = fixup_offset; LOGF(" : +depth %" PRIzd "\n", ctx->emit_label_stack.size - 1); return WASM_OK; } static void pop_emit_label(WasmContext* ctx) { LOGF(" : -depth %" PRIzd "\n", ctx->emit_label_stack.size - 1); assert(ctx->emit_label_stack.size > 0); ctx->emit_label_stack.size--; /* reduce the depth_fixups stack as well, but it may be smaller than * emit_label_stack so only do it conditionally. */ if (ctx->depth_fixups.size > ctx->emit_label_stack.size) { uint32_t from = ctx->emit_label_stack.size; uint32_t to = ctx->depth_fixups.size; uint32_t i; for (i = from; i < to; ++i) wasm_destroy_uint32_vector(ctx->allocator, &ctx->depth_fixups.data[i]); ctx->depth_fixups.size = ctx->emit_label_stack.size; } } static WasmResult fixup_top_emit_label(WasmContext* ctx, uint32_t offset) { uint32_t top = ctx->emit_label_stack.size - 1; if (top >= ctx->depth_fixups.size) { /* nothing to fixup */ return WASM_OK; } WasmUint32Vector* fixups = &ctx->depth_fixups.data[top]; uint32_t i; for (i = 0; i < fixups->size; ++i) CHECK_RESULT(emit_i32_at(ctx, fixups->data[i], offset)); /* reduce the size to 0 in case this gets reused. Keep the allocations for * later use */ fixups->size = 0; return WASM_OK; } static WasmResult emit_discard(WasmContext* ctx) { LOGF("%3" PRIzd ": discard\n", ctx->value_stack_size); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_DISCARD)); adjust_value_stack(ctx, -1); return WASM_OK; } static WasmResult maybe_emit_discard(WasmContext* ctx, uint32_t expr_index) { WasmBool should_discard = is_expr_discarded(ctx, expr_index); if (should_discard) return emit_discard(ctx); return WASM_OK; } static WasmResult emit_discard_keep(WasmContext* ctx, uint32_t discard, uint8_t keep) { assert(discard != UINT32_MAX); assert(keep <= 1); if (discard > 0) { if (discard == 1 && keep == 0) { LOGF("%3" PRIzd ": discard\n", ctx->value_stack_size); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_DISCARD)); } else { LOGF("%3" PRIzd ": discard_keep %u %u\n", ctx->value_stack_size, discard, keep); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_DISCARD_KEEP)); CHECK_RESULT(emit_i32(ctx, discard)); CHECK_RESULT(emit_i8(ctx, keep)); } } return WASM_OK; } static WasmResult emit_return(WasmContext* ctx, WasmType result_type) { uint32_t keep_count = get_value_count(result_type); uint32_t discard_count = ctx->value_stack_size - keep_count; CHECK_RESULT(emit_discard_keep(ctx, discard_count, keep_count)); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_RETURN)); return WASM_OK; } static WasmResult append_fixup(WasmContext* ctx, WasmUint32VectorVector* fixups_vector, uint32_t index) { if (index >= fixups_vector->size) { CHECK_ALLOC(ctx, wasm_resize_uint32_vector_vector( ctx->allocator, fixups_vector, index + 1)); } WasmUint32Vector* fixups = &fixups_vector->data[index]; uint32_t offset = get_istream_offset(ctx); CHECK_ALLOC(ctx, wasm_append_uint32_value(ctx->allocator, fixups, &offset)); return WASM_OK; } static WasmResult emit_br_offset(WasmContext* ctx, uint32_t depth, uint32_t offset) { if (offset == WASM_INVALID_OFFSET) CHECK_RESULT(append_fixup(ctx, &ctx->depth_fixups, depth)); CHECK_RESULT(emit_i32(ctx, offset)); return WASM_OK; } static WasmResult emit_br(WasmContext* ctx, uint32_t depth) { WasmEmitLabel* label = get_emit_label(ctx, depth); assert(ctx->value_stack_size >= label->value_stack_size); uint8_t keep_count = label->has_value ? 1 : 0; uint32_t discard_count = (ctx->value_stack_size - label->value_stack_size) - keep_count; CHECK_RESULT(emit_discard_keep(ctx, discard_count, keep_count)); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_BR)); CHECK_RESULT(emit_br_offset(ctx, depth, label->offset)); return WASM_OK; } static WasmResult emit_br_table_offset(WasmContext* ctx, uint32_t depth) { WasmEmitLabel* label = get_emit_label(ctx, depth); uint8_t keep_count = label->has_value ? 1 : 0; uint32_t discard_count = (ctx->value_stack_size - label->value_stack_size) - keep_count; CHECK_RESULT(emit_br_offset(ctx, depth, label->offset)); CHECK_RESULT(emit_i32(ctx, discard_count)); CHECK_RESULT(emit_i8(ctx, keep_count)); return WASM_OK; } static WasmResult emit_func_offset(WasmContext* ctx, WasmInterpreterFunc* func, uint32_t func_index) { if (func->offset == WASM_INVALID_OFFSET) CHECK_RESULT(append_fixup(ctx, &ctx->func_fixups, func_index)); CHECK_RESULT(emit_i32(ctx, func->offset)); return WASM_OK; } static WasmResult begin_emit_function_body(uint32_t index, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFunc* func = get_func(ctx, index); WasmInterpreterFuncSignature* sig = get_signature(ctx, func->sig_index); func->offset = get_istream_offset(ctx); func->local_decl_count = 0; func->local_count = 0; ctx->current_func = func; ctx->emit_label_stack.size = 0; ctx->depth_fixups.size = 0; ctx->value_stack_size = sig->param_types.size; ctx->depth = 0; ctx->expr_count = 0; /* fixup function references */ uint32_t i; WasmUint32Vector* fixups = &ctx->func_fixups.data[index]; for (i = 0; i < fixups->size; ++i) CHECK_RESULT(emit_i32_at(ctx, fixups->data[i], func->offset)); return WASM_OK; } static WasmResult end_emit_function_body(uint32_t index, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterFuncSignature* sig = get_func_signature(ctx, ctx->current_func); CHECK_RESULT(emit_return(ctx, sig->result_type)); ctx->current_func = NULL; ctx->value_stack_size = 0; return WASM_OK; } static WasmResult on_emit_local_decl_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; ctx->current_func->local_decl_count = count; return WASM_OK; } static WasmResult on_emit_local_decl(uint32_t decl_index, uint32_t count, WasmType type, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_ALLOCA]); WasmInterpreterFunc* func = ctx->current_func; func->local_count += count; if (decl_index == func->local_decl_count - 1) { /* last local declaration, allocate space for all locals. */ CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_ALLOCA)); CHECK_RESULT(emit_i32(ctx, func->local_count)); adjust_value_stack(ctx, func->local_count); } return WASM_OK; } static WasmResult on_emit_unary_expr(WasmOpcode opcode, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[opcode]); CHECK_RESULT(emit_opcode(ctx, opcode)); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_binary_expr(WasmOpcode opcode, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[opcode]); adjust_value_stack(ctx, -1); CHECK_RESULT(emit_opcode(ctx, opcode)); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_block_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_BLOCK]); CHECK_RESULT(push_emit_label(ctx, WASM_LABEL_TYPE_BLOCK, WASM_INVALID_OFFSET, 0, !is_expr_discarded(ctx, ctx->expr_count))); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_loop_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_LOOP]); CHECK_RESULT(push_emit_label(ctx, WASM_LABEL_TYPE_LOOP, WASM_INVALID_OFFSET, 0, !is_expr_discarded(ctx, ctx->expr_count))); CHECK_RESULT(push_emit_label(ctx, WASM_LABEL_TYPE_LOOP, get_istream_offset(ctx), 0, WASM_FALSE)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_if_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_IF]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_BR_UNLESS)); adjust_value_stack(ctx, -1); uint32_t fixup_offset = get_istream_offset(ctx); CHECK_RESULT(emit_i32(ctx, WASM_INVALID_OFFSET)); CHECK_RESULT(push_emit_label(ctx, WASM_LABEL_TYPE_IF, WASM_INVALID_OFFSET, fixup_offset, !is_expr_discarded(ctx, ctx->expr_count))); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_else_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_ELSE]); WasmEmitLabel* label = top_emit_label(ctx); assert(label->label_type == WASM_LABEL_TYPE_IF); label->label_type = WASM_LABEL_TYPE_ELSE; uint32_t fixup_cond_offset = label->fixup_offset; CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_BR)); label->fixup_offset = get_istream_offset(ctx); CHECK_RESULT(emit_i32(ctx, WASM_INVALID_OFFSET)); CHECK_RESULT(emit_i32_at(ctx, fixup_cond_offset, get_istream_offset(ctx))); /* reset the value stack for the other branch arm */ ctx->value_stack_size = label->value_stack_size; return WASM_OK; } static WasmResult on_emit_end_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_END]); WasmEmitLabel* label = top_emit_label(ctx); switch (label->label_type) { case WASM_LABEL_TYPE_LOOP: /* pop the continue label */ pop_emit_label(ctx); label = top_emit_label(ctx); assert(label->label_type == WASM_LABEL_TYPE_LOOP); break; case WASM_LABEL_TYPE_IF: case WASM_LABEL_TYPE_ELSE: { uint32_t fixup_true_offset = label->fixup_offset; CHECK_RESULT( emit_i32_at(ctx, fixup_true_offset, get_istream_offset(ctx))); break; } default: break; } fixup_top_emit_label(ctx, get_istream_offset(ctx)); ctx->value_stack_size = label->value_stack_size; adjust_value_stack(ctx, label->has_value ? 1 : 0); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); pop_emit_label(ctx); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_br_expr(uint32_t depth, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_BR]); CHECK_RESULT(emit_br(ctx, translate_emit_depth(ctx, depth))); /* non-local continuation, so it's not necessary to adjust the value stack */ ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_br_if_expr(uint32_t depth, void* user_data) { /* flip the br_if so if is true it can discard values from the stack */ WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_BR_IF]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_BR_UNLESS)); adjust_value_stack(ctx, -1); /* account for br_unless consuming */ uint32_t fixup_br_offset = get_istream_offset(ctx); CHECK_RESULT(emit_i32(ctx, WASM_INVALID_OFFSET)); CHECK_RESULT(emit_br(ctx, translate_emit_depth(ctx, depth))); CHECK_RESULT(emit_i32_at(ctx, fixup_br_offset, get_istream_offset(ctx))); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_br_table_expr(uint32_t num_targets, uint32_t* target_depths, uint32_t default_target_depth, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_BR_TABLE]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_BR_TABLE)); CHECK_RESULT(emit_i32(ctx, num_targets)); uint32_t fixup_table_offset = get_istream_offset(ctx); CHECK_RESULT(emit_i32(ctx, WASM_INVALID_OFFSET)); adjust_value_stack(ctx, -1); /* not necessary for the interpreter, but it makes it easier to disassemble. * This opcode specifies how many bytes of data follow. */ CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_DATA)); CHECK_RESULT(emit_i32(ctx, (num_targets + 1) * WASM_TABLE_ENTRY_SIZE)); CHECK_RESULT(emit_i32_at(ctx, fixup_table_offset, get_istream_offset(ctx))); /* write the branch table as (offset, discard count) pairs */ uint32_t i; for (i = 0; i <= num_targets; ++i) { uint32_t depth = i != num_targets ? target_depths[i] : default_target_depth; CHECK_RESULT(emit_br_table_offset(ctx, translate_emit_depth(ctx, depth))); } ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_call_expr(uint32_t func_index, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_CALL_FUNCTION]); WasmInterpreterFunc* func = get_func(ctx, func_index); WasmInterpreterFuncSignature* sig = get_func_signature(ctx, func); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_CALL_FUNCTION)); CHECK_RESULT(emit_func_offset(ctx, func, func_index)); adjust_value_stack(ctx, get_value_count(sig->result_type) - sig->param_types.size); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_call_import_expr(uint32_t import_index, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_CALL_IMPORT]); WasmInterpreterImport* import = get_import(ctx, import_index); WasmInterpreterFuncSignature* sig = get_signature(ctx, import->sig_index); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_CALL_IMPORT)); CHECK_RESULT(emit_i32(ctx, import_index)); adjust_value_stack(ctx, get_value_count(sig->result_type) - sig->param_types.size); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_call_indirect_expr(uint32_t sig_index, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_CALL_INDIRECT]); WasmInterpreterFuncSignature* sig = get_signature(ctx, sig_index); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_CALL_INDIRECT)); CHECK_RESULT(emit_i32(ctx, sig_index)); uint32_t result_count = get_value_count(sig->result_type); /* the callee cleans up the params for us, but we have to clean up the * function table index */ adjust_value_stack(ctx, result_count - sig->param_types.size); CHECK_RESULT(emit_discard_keep(ctx, 1, result_count)); adjust_value_stack(ctx, -1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_i32_const_expr(uint32_t value, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_I32_CONST]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_I32_CONST)); CHECK_RESULT(emit_i32(ctx, value)); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_i64_const_expr(uint64_t value, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_I64_CONST]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_I64_CONST)); CHECK_RESULT(emit_i64(ctx, value)); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_f32_const_expr(uint32_t value_bits, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_F32_CONST]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_F32_CONST)); CHECK_RESULT(emit_i32(ctx, value_bits)); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_f64_const_expr(uint64_t value_bits, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_F64_CONST]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_F64_CONST)); CHECK_RESULT(emit_i64(ctx, value_bits)); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_get_local_expr(uint32_t local_index, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_GET_LOCAL]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_GET_LOCAL)); CHECK_RESULT(emit_i32(ctx, translate_local_index(ctx, local_index))); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_set_local_expr(uint32_t local_index, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_SET_LOCAL]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_SET_LOCAL)); CHECK_RESULT(emit_i32(ctx, translate_local_index(ctx, local_index))); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_grow_memory_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_GROW_MEMORY]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_GROW_MEMORY)); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_load_expr(WasmOpcode opcode, uint32_t alignment_log2, uint32_t offset, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[opcode]); CHECK_RESULT(emit_opcode(ctx, opcode)); CHECK_RESULT(emit_i32(ctx, offset)); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_store_expr(WasmOpcode opcode, uint32_t alignment_log2, uint32_t offset, void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[opcode]); CHECK_RESULT(emit_opcode(ctx, opcode)); CHECK_RESULT(emit_i32(ctx, offset)); adjust_value_stack(ctx, -1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_memory_size_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_MEMORY_SIZE]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_MEMORY_SIZE)); adjust_value_stack(ctx, 1); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_nop_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_NOP]); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_return_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_RETURN]); WasmInterpreterFuncSignature* sig = get_func_signature(ctx, ctx->current_func); CHECK_RESULT(emit_return(ctx, sig->result_type)); /* non-local continuation, so it's not necessary to adjust the value stack */ CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_select_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_SELECT]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_SELECT)); CHECK_RESULT(maybe_emit_discard(ctx, ctx->expr_count)); adjust_value_stack(ctx, -2); ctx->expr_count++; return WASM_OK; } static WasmResult on_emit_unreachable_expr(void* user_data) { WasmContext* ctx = user_data; LOGF("%3" PRIzd ": %s\n", ctx->value_stack_size, s_opcode_name[WASM_OPCODE_UNREACHABLE]); CHECK_RESULT(emit_opcode(ctx, WASM_OPCODE_UNREACHABLE)); /* adjust stack up; unreachable type-checks as ANY, so it can be used in any * operation. No value will actually be pushed, and the expressions that use * the result won't ever be executed. But it will make the stack the "normal" * size, so we won't have to special case it anywhere else. */ adjust_value_stack(ctx, is_expr_discarded(ctx, ctx->expr_count) ? 0 : 1); ctx->expr_count++; return WASM_OK; } static WasmResult on_function_table_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; CHECK_ALLOC(ctx, wasm_new_interpreter_func_table_entry_array( ctx->allocator, &ctx->module->func_table, count)); return WASM_OK; } static WasmResult on_function_table_entry(uint32_t index, uint32_t func_index, void* user_data) { WasmContext* ctx = user_data; assert(index < ctx->module->func_table.size); WasmInterpreterFuncTableEntry* entry = &ctx->module->func_table.data[index]; WasmInterpreterFunc* func = get_func(ctx, func_index); entry->sig_index = func->sig_index; /* the function offset isn't known yet, so temporarily store the func index * in func_offset and resolve after the last function body */ entry->func_offset = func_index; return WASM_OK; } static WasmResult on_start_function(uint32_t func_index, void* user_data) { WasmContext* ctx = user_data; /* can't get the function offset yet, because we haven't parsed the * functions. Just store the function index and resolve it later in * end_function_bodies_section. */ assert(func_index < ctx->funcs.size); ctx->start_func_index = func_index; return WASM_OK; } static WasmResult on_export_count(uint32_t count, void* user_data) { WasmContext* ctx = user_data; CHECK_ALLOC(ctx, wasm_new_interpreter_export_array( ctx->allocator, &ctx->module->exports, count)); return WASM_OK; } static WasmResult on_export(uint32_t index, uint32_t func_index, WasmStringSlice name, void* user_data) { WasmContext* ctx = user_data; WasmInterpreterExport* export = &ctx->module->exports.data[index]; WasmInterpreterFunc* func = get_func(ctx, func_index); CHECK_ALLOC_NULL_STR( ctx, export->name = wasm_dup_string_slice(ctx->allocator, name)); export->func_index = func_index; export->sig_index = func->sig_index; export->func_offset = WASM_INVALID_OFFSET; return WASM_OK; } static WasmResult end_function_bodies_section(void* user_data) { WasmContext* ctx = user_data; /* resolve the start function offset */ if (ctx->start_func_index != INVALID_FUNC_INDEX) { WasmInterpreterFunc* func = get_func(ctx, ctx->start_func_index); assert(func->offset != WASM_INVALID_OFFSET); ctx->module->start_func_offset = func->offset; } /* resolve the export function offsets */ uint32_t i; for (i = 0; i < ctx->module->exports.size; ++i) { WasmInterpreterExport* export = get_export(ctx, i); WasmInterpreterFunc* func = get_func(ctx, export->func_index); export->func_offset = func->offset; } /* resolve the function table entry offsets */ for (i = 0; i < ctx->module->func_table.size; ++i) { WasmInterpreterFuncTableEntry* entry = &ctx->module->func_table.data[i]; /* function index is stored in func_offset temporarily */ WasmInterpreterFunc* func = get_func(ctx, entry->func_offset); entry->func_offset = func->offset; } return WASM_OK; } static WasmBinaryReader s_binary_reader = { .user_data = NULL, .on_error = &on_error, .on_memory_initial_size_pages = &on_memory_initial_size_pages, .on_data_segment = &on_data_segment, .on_signature_count = &on_signature_count, .on_signature = &on_signature, .on_import_count = &on_import_count, .on_import = &on_import, .on_function_signatures_count = &on_function_signatures_count, .on_function_signature = &on_function_signature, .on_function_bodies_count = &on_function_bodies_count, .begin_function_body_pass = &begin_function_body_pass, .begin_function_body = &begin_function_body, .on_local_decl = &on_local_decl, .on_binary_expr = &on_binary_expr, .on_block_expr = &on_block_expr, .on_br_expr = &on_br_expr, .on_br_if_expr = &on_br_if_expr, .on_br_table_expr = &on_br_table_expr, .on_call_expr = &on_call_expr, .on_call_import_expr = &on_call_import_expr, .on_call_indirect_expr = &on_call_indirect_expr, .on_compare_expr = &on_binary_expr, .on_convert_expr = &on_unary_expr, .on_else_expr = &on_else_expr, .on_end_expr = &on_end_expr, .on_f32_const_expr = &on_f32_const_expr, .on_f64_const_expr = &on_f64_const_expr, .on_get_local_expr = &on_get_local_expr, .on_grow_memory_expr = &on_grow_memory_expr, .on_i32_const_expr = &on_i32_const_expr, .on_i64_const_expr = &on_i64_const_expr, .on_if_expr = &on_if_expr, .on_load_expr = &on_load_expr, .on_loop_expr = &on_loop_expr, .on_memory_size_expr = &on_memory_size_expr, .on_nop_expr = &on_nop_expr, .on_return_expr = &on_return_expr, .on_select_expr = &on_select_expr, .on_set_local_expr = &on_set_local_expr, .on_store_expr = &on_store_expr, .on_unary_expr = &on_unary_expr, .on_unreachable_expr = &on_unreachable_expr, .end_function_body = &end_function_body, .end_function_bodies_section = &end_function_bodies_section, .end_function_body_pass = &end_function_body_pass, .on_function_table_count = &on_function_table_count, .on_function_table_entry = &on_function_table_entry, .on_start_function = &on_start_function, .on_export_count = &on_export_count, .on_export = &on_export, }; static WasmBinaryReader s_binary_reader_emit = { .on_error = &on_error, .begin_function_body_pass = &begin_function_body_pass, .begin_function_body = &begin_emit_function_body, .on_local_decl_count = &on_emit_local_decl_count, .on_local_decl = &on_emit_local_decl, .on_binary_expr = &on_emit_binary_expr, .on_block_expr = &on_emit_block_expr, .on_br_expr = &on_emit_br_expr, .on_br_if_expr = &on_emit_br_if_expr, .on_br_table_expr = &on_emit_br_table_expr, .on_call_expr = &on_emit_call_expr, .on_call_import_expr = &on_emit_call_import_expr, .on_call_indirect_expr = &on_emit_call_indirect_expr, .on_compare_expr = &on_emit_binary_expr, .on_convert_expr = &on_emit_unary_expr, .on_else_expr = &on_emit_else_expr, .on_end_expr = &on_emit_end_expr, .on_f32_const_expr = &on_emit_f32_const_expr, .on_f64_const_expr = &on_emit_f64_const_expr, .on_get_local_expr = &on_emit_get_local_expr, .on_grow_memory_expr = &on_emit_grow_memory_expr, .on_i32_const_expr = &on_emit_i32_const_expr, .on_i64_const_expr = &on_emit_i64_const_expr, .on_if_expr = &on_emit_if_expr, .on_load_expr = &on_emit_load_expr, .on_loop_expr = &on_emit_loop_expr, .on_memory_size_expr = &on_emit_memory_size_expr, .on_nop_expr = &on_emit_nop_expr, .on_return_expr = &on_emit_return_expr, .on_select_expr = &on_emit_select_expr, .on_set_local_expr = &on_emit_set_local_expr, .on_store_expr = &on_emit_store_expr, .on_unary_expr = &on_emit_unary_expr, .on_unreachable_expr = &on_emit_unreachable_expr, .end_function_body = &end_emit_function_body, .end_function_body_pass = &end_function_body_pass, }; static WasmResult begin_function_body_pass(uint32_t index, uint32_t pass, void* user_data) { LOGF("*** func %d pass %d ***\n", index, pass); WasmContext* ctx = user_data; assert(pass < 2); *ctx->reader = pass == 0 ? s_binary_reader : s_binary_reader_emit; ctx->reader->user_data = user_data; return WASM_OK; } static WasmResult end_function_body_pass(uint32_t index, uint32_t pass, void* user_data) { WasmContext* ctx = user_data; /* reset the reader to its original callbacks */ if (pass == 1) *ctx->reader = s_binary_reader; ctx->reader->user_data = user_data; return WASM_OK; } static void wasm_destroy_interpreter_func(WasmAllocator* allocator, WasmInterpreterFunc* func) { wasm_destroy_type_vector(allocator, &func->param_and_local_types); } static void destroy_context(WasmContext* ctx) { wasm_destroy_uint32_vector(ctx->allocator, &ctx->discarded_exprs); wasm_destroy_expr_node_vector(ctx->allocator, &ctx->expr_stack); wasm_destroy_emit_label_vector(ctx->allocator, &ctx->emit_label_stack); wasm_destroy_typecheck_label_vector(ctx->allocator, &ctx->typecheck_label_stack); WASM_DESTROY_ARRAY_AND_ELEMENTS(ctx->allocator, ctx->funcs, interpreter_func); WASM_DESTROY_VECTOR_AND_ELEMENTS(ctx->allocator, ctx->depth_fixups, uint32_vector); WASM_DESTROY_VECTOR_AND_ELEMENTS(ctx->allocator, ctx->func_fixups, uint32_vector); } WasmResult wasm_read_binary_interpreter(WasmAllocator* allocator, WasmAllocator* memory_allocator, const void* data, size_t size, const WasmReadBinaryOptions* options, WasmBinaryErrorHandler* error_handler, WasmInterpreterModule* out_module) { WasmContext ctx; WasmBinaryReader reader; WASM_ZERO_MEMORY(ctx); WASM_ZERO_MEMORY(reader); ctx.allocator = allocator; ctx.reader = &reader; ctx.error_handler = error_handler; ctx.memory_allocator = memory_allocator; ctx.module = out_module; ctx.start_func_index = INVALID_FUNC_INDEX; ctx.module->start_func_offset = WASM_INVALID_OFFSET; CHECK_RESULT(wasm_init_mem_writer(allocator, &ctx.istream_writer)); reader = s_binary_reader; reader.user_data = &ctx; const uint32_t num_function_passes = 2; WasmResult result = wasm_read_binary(allocator, data, size, &reader, num_function_passes, options); if (WASM_SUCCEEDED(result)) { wasm_steal_mem_writer_output_buffer(&ctx.istream_writer, &out_module->istream); out_module->istream.size = ctx.istream_offset; } destroy_context(&ctx); return result; }