diff options
Diffstat (limited to 'src/tools/wasm-interp.c')
-rw-r--r-- | src/tools/wasm-interp.c | 1322 |
1 files changed, 885 insertions, 437 deletions
diff --git a/src/tools/wasm-interp.c b/src/tools/wasm-interp.c index e50772b9..e4778145 100644 --- a/src/tools/wasm-interp.c +++ b/src/tools/wasm-interp.c @@ -23,6 +23,7 @@ #include "wasm-binary-reader.h" #include "wasm-binary-reader-interpreter.h" #include "wasm-interpreter.h" +#include "wasm-literal.h" #include "wasm-option-parser.h" #include "wasm-stack-allocator.h" #include "wasm-stream.h" @@ -201,27 +202,32 @@ static WasmStringSlice get_dirname(const char* s) { return result; } -static void print_typed_value(WasmInterpreterTypedValue* tv) { +/* Not sure, but 100 chars is probably safe */ +#define MAX_TYPED_VALUE_CHARS 100 + +static void sprint_typed_value(char* buffer, + size_t size, + const WasmInterpreterTypedValue* tv) { switch (tv->type) { case WASM_TYPE_I32: - printf("i32:%u", tv->value.i32); + wasm_snprintf(buffer, size, "i32:%u", tv->value.i32); break; case WASM_TYPE_I64: - printf("i64:%" PRIu64, tv->value.i64); + wasm_snprintf(buffer, size, "i64:%" PRIu64, tv->value.i64); break; case WASM_TYPE_F32: { float value; memcpy(&value, &tv->value.f32_bits, sizeof(float)); - printf("f32:%g", value); + wasm_snprintf(buffer, size, "f32:%g", value); break; } case WASM_TYPE_F64: { double value; memcpy(&value, &tv->value.f64_bits, sizeof(double)); - printf("f64:%g", value); + wasm_snprintf(buffer, size, "f64:%g", value); break; } @@ -231,6 +237,54 @@ static void print_typed_value(WasmInterpreterTypedValue* tv) { } } + +static void print_typed_value(const WasmInterpreterTypedValue* tv) { + char buffer[MAX_TYPED_VALUE_CHARS]; + sprint_typed_value(buffer, sizeof(buffer), tv); + printf("%s", buffer); +} + +static void print_typed_values(const WasmInterpreterTypedValue* values, + size_t num_values) { + size_t i; + for (i = 0; i < num_values; ++i) { + print_typed_value(&values[i]); + if (i != num_values - 1) + printf(", "); + } +} + +static void print_typed_value_vector( + const WasmInterpreterTypedValueVector* values) { + print_typed_values(&values->data[0], values->size); +} + +static void print_interpreter_result(const char* desc, + WasmInterpreterResult iresult) { + printf("%s: %s\n", desc, s_trap_strings[iresult]); +} + +static void print_call(WasmStringSlice module_name, + WasmStringSlice func_name, + const WasmInterpreterTypedValueVector* args, + const WasmInterpreterTypedValueVector* results, + WasmInterpreterResult iresult) { + if (module_name.length) + printf(PRIstringslice ".", WASM_PRINTF_STRING_SLICE_ARG(module_name)); + printf(PRIstringslice "(", WASM_PRINTF_STRING_SLICE_ARG(func_name)); + print_typed_value_vector(args); + printf(") =>"); + if (iresult == WASM_INTERPRETER_OK) { + if (results->size > 0) { + printf(" "); + print_typed_value_vector(results); + } + printf("\n"); + } else { + print_interpreter_result(" error", iresult); + } +} + static WasmInterpreterResult run_defined_function(WasmInterpreterThread* thread, uint32_t offset) { thread->pc = offset; @@ -242,167 +296,149 @@ static WasmInterpreterResult run_defined_function(WasmInterpreterThread* thread, wasm_trace_pc(thread, s_stdout_stream); iresult = wasm_run_interpreter(thread, quantum, call_stack_return_top); } - if (iresult != WASM_INTERPRETER_RETURNED) { - if (s_trace) - printf("!!! trapped: %s\n", s_trap_strings[iresult]); + if (iresult != WASM_INTERPRETER_RETURNED) return iresult; - } /* use OK instead of RETURNED for consistency */ return WASM_INTERPRETER_OK; } -static WasmInterpreterResult run_function(WasmInterpreterThread* thread, - uint32_t func_index) { - assert(func_index < thread->env->funcs.size); - WasmInterpreterFunc* func = &thread->env->funcs.data[func_index]; - if (func->is_host) - return wasm_call_host(thread, func); - else - return run_defined_function(thread, func->defined.offset); -} +static WasmInterpreterResult push_args( + WasmInterpreterThread* thread, + const WasmInterpreterFuncSignature* sig, + const WasmInterpreterTypedValueVector* args) { + if (sig->param_types.size != args->size) + return WASM_INTERPRETER_ARGUMENT_TYPE_MISMATCH; + + size_t i; + for (i = 0; i < sig->param_types.size; ++i) { + if (sig->param_types.data[i] != args->data[i].type) + return WASM_INTERPRETER_ARGUMENT_TYPE_MISMATCH; -static WasmResult run_start_function(WasmInterpreterThread* thread) { - WasmResult result = WASM_OK; - if (thread->module->defined.start_func_index != WASM_INVALID_INDEX) { - if (s_trace) - printf(">>> running start function:\n"); WasmInterpreterResult iresult = - run_function(thread, thread->module->defined.start_func_index); + wasm_push_thread_value(thread, args->data[i].value); if (iresult != WASM_INTERPRETER_OK) { - /* trap */ - fprintf(stderr, "error: %s\n", s_trap_strings[iresult]); - result = WASM_ERROR; + thread->value_stack_top = thread->value_stack.data; + return iresult; } } - return result; + return WASM_INTERPRETER_OK; } -static WasmInterpreterFuncSignature* get_export_signature( - WasmInterpreterEnvironment* env, - WasmInterpreterExport* export) { - assert(export->kind == WASM_EXTERNAL_KIND_FUNC); - uint32_t func_index = export->index; - uint32_t sig_index = env->funcs.data[func_index].sig_index; - assert(sig_index < env->sigs.size); - return &env->sigs.data[sig_index]; +static void copy_results(WasmAllocator* allocator, + WasmInterpreterThread* thread, + const WasmInterpreterFuncSignature* sig, + WasmInterpreterTypedValueVector* out_results) { + size_t expected_results = sig->result_types.size; + size_t value_stack_depth = thread->value_stack_top - thread->value_stack.data; + WASM_USE(value_stack_depth); + assert(expected_results == value_stack_depth); + + /* Don't clear out the vector, in case it is being reused. Just reset the + * size to zero. */ + out_results->size = 0; + wasm_resize_interpreter_typed_value_vector(allocator, out_results, + expected_results); + size_t i; + for (i = 0; i < expected_results; ++i) { + out_results->data[i].type = sig->result_types.data[i]; + out_results->data[i].value = thread->value_stack.data[i]; + } } -static WasmInterpreterResult run_export( +static WasmInterpreterResult run_function( WasmAllocator* allocator, WasmInterpreterThread* thread, - WasmInterpreterExport* export, + uint32_t func_index, + const WasmInterpreterTypedValueVector* args, WasmInterpreterTypedValueVector* out_results) { - assert(export->kind == WASM_EXTERNAL_KIND_FUNC); - WasmInterpreterFuncSignature* sig = get_export_signature(thread->env, export); - - /* push all 0 values as arguments */ - assert(sig->param_types.size < thread->value_stack.size); - size_t num_args = sig->param_types.size; - thread->value_stack_top = &thread->value_stack.data[num_args]; - memset(thread->value_stack.data, 0, num_args * sizeof(WasmInterpreterValue)); - - WasmInterpreterResult result = run_function(thread, export->index); - - if (result == WASM_INTERPRETER_OK) { - size_t expected_results = sig->result_types.size; - size_t value_stack_depth = - thread->value_stack_top - thread->value_stack.data; - WASM_USE(value_stack_depth); - assert(expected_results == value_stack_depth); - - if (out_results) { - wasm_resize_interpreter_typed_value_vector(allocator, out_results, - expected_results); - size_t i; - for (i = 0; i < expected_results; ++i) { - WasmInterpreterTypedValue actual_result; - actual_result.type = sig->result_types.data[i]; - actual_result.value = thread->value_stack.data[i]; - - if (out_results) { - /* copy as many results as the caller wants */ - out_results->data[i] = actual_result; - } - } - } + assert(func_index < thread->env->funcs.size); + WasmInterpreterFunc* func = &thread->env->funcs.data[func_index]; + uint32_t sig_index = func->sig_index; + assert(sig_index < thread->env->sigs.size); + WasmInterpreterFuncSignature* sig = &thread->env->sigs.data[sig_index]; + + WasmInterpreterResult iresult = push_args(thread, sig, args); + if (iresult == WASM_INTERPRETER_OK) { + iresult = func->is_host + ? wasm_call_host(thread, func) + : run_defined_function(thread, func->defined.offset); + if (iresult == WASM_INTERPRETER_OK) + copy_results(allocator, thread, sig, out_results); } + /* Always reset the value and call stacks */ thread->value_stack_top = thread->value_stack.data; thread->call_stack_top = thread->call_stack.data; + return iresult; +} - return result; +static WasmInterpreterResult run_start_function(WasmAllocator* allocator, + WasmInterpreterThread* thread) { + if (thread->module->defined.start_func_index == WASM_INVALID_INDEX) + return WASM_INTERPRETER_OK; + + if (s_trace) + printf(">>> running start function:\n"); + WasmInterpreterTypedValueVector args; + WasmInterpreterTypedValueVector results; + WASM_ZERO_MEMORY(args); + WASM_ZERO_MEMORY(results); + + WasmInterpreterResult iresult = + run_function(allocator, thread, thread->module->defined.start_func_index, + &args, &results); + assert(results.size == 0); + return iresult; } -static WasmInterpreterResult run_export_wrapper( +static WasmInterpreterResult run_export( WasmAllocator* allocator, WasmInterpreterThread* thread, - WasmInterpreterExport* export, - WasmInterpreterTypedValueVector* out_results, - RunVerbosity verbose) { + const WasmInterpreterExport* export, + const WasmInterpreterTypedValueVector* args, + WasmInterpreterTypedValueVector* out_results) { if (s_trace) { printf(">>> running export \"" PRIstringslice "\":\n", WASM_PRINTF_STRING_SLICE_ARG(export->name)); } - WasmInterpreterResult result = - run_export(allocator, thread, export, out_results); - - if (verbose) { - WasmInterpreterFuncSignature* sig = - get_export_signature(thread->env, export); - printf(PRIstringslice "(", WASM_PRINTF_STRING_SLICE_ARG(export->name)); - size_t i; - for (i = 0; i < sig->param_types.size; ++i) { - printf("0"); - if (i != sig->param_types.size - 1) - printf(", "); - } - printf(") => "); - - if (result == WASM_INTERPRETER_OK) { - assert(out_results); - for (i = 0; i < out_results->size; ++i) { - print_typed_value(&out_results->data[i]); - if (i != out_results->size - 1) - printf(", "); - } - printf("\n"); - } else { - /* trap */ - printf("error: %s\n", s_trap_strings[result]); - } - } - return result; + assert(export->kind == WASM_EXTERNAL_KIND_FUNC); + return run_function(allocator, thread, export->index, args, out_results); } -static WasmResult run_export_by_name( +static WasmInterpreterResult run_export_by_name( WasmAllocator* allocator, WasmInterpreterThread* thread, - WasmStringSlice* name, - WasmInterpreterResult* out_iresult, + const WasmStringSlice* name, + const WasmInterpreterTypedValueVector* args, WasmInterpreterTypedValueVector* out_results, RunVerbosity verbose) { WasmInterpreterExport* export = wasm_get_interpreter_export_by_name(thread->module, name); if (!export) - return WASM_ERROR; - - *out_iresult = - run_export_wrapper(allocator, thread, export, out_results, verbose); - return WASM_OK; + return WASM_INTERPRETER_UNKNOWN_EXPORTED_FUNCTION; + return run_export(allocator, thread, export, args, out_results); } static void run_all_exports(WasmAllocator* allocator, WasmInterpreterModule* module, WasmInterpreterThread* thread, RunVerbosity verbose) { + WasmInterpreterTypedValueVector args; WasmInterpreterTypedValueVector results; + WASM_ZERO_MEMORY(args); WASM_ZERO_MEMORY(results); uint32_t i; for (i = 0; i < module->exports.size; ++i) { WasmInterpreterExport* export = &module->exports.data[i]; - run_export_wrapper(allocator, thread, export, &results, verbose); + WasmInterpreterResult iresult = + run_export(allocator, thread, export, &args, &results); + if (verbose) { + print_call(wasm_empty_string_slice(), export->name, &args, &results, + iresult); + } } + wasm_destroy_interpreter_typed_value_vector(allocator, &args); wasm_destroy_interpreter_typed_value_vector(allocator, &results); } @@ -434,16 +470,6 @@ static WasmResult read_module(WasmAllocator* allocator, return result; } -static void print_typed_values(WasmInterpreterTypedValue* values, - size_t num_values) { - uint32_t i; - for (i = 0; i < num_values; ++i) { - print_typed_value(&values[i]); - if (i != num_values - 1) - printf(", "); - } -} - static WasmResult default_host_callback(const WasmInterpreterFunc* func, const WasmInterpreterFuncSignature* sig, uint32_t num_args, @@ -456,13 +482,17 @@ static WasmResult default_host_callback(const WasmInterpreterFunc* func, for (i = 0; i < num_results; ++i) out_results[i].type = sig->result_types.data[i]; - printf("called host " PRIstringslice "." PRIstringslice "(", - WASM_PRINTF_STRING_SLICE_ARG(func->host.module_name), - WASM_PRINTF_STRING_SLICE_ARG(func->host.field_name)); - print_typed_values(args, num_args); - printf(") => ("); - print_typed_values(out_results, num_results); - printf(")\n"); + WasmInterpreterTypedValueVector vec_args; + vec_args.size = num_args; + vec_args.data = args; + + WasmInterpreterTypedValueVector vec_results; + vec_results.size = num_results; + vec_results.data = out_results; + + printf("called host "); + print_call(func->host.module_name, func->host.field_name, &vec_args, + &vec_results, WASM_INTERPRETER_OK); return WASM_OK; } @@ -513,356 +543,774 @@ static WasmResult read_and_run_module(WasmAllocator* allocator, init_environment(allocator, &env); result = read_module(allocator, module_filename, &env, &module, &thread); if (WASM_SUCCEEDED(result)) { - result = run_start_function(&thread); - if (s_run_all_exports) - run_all_exports(allocator, module, &thread, RUN_VERBOSE); + WasmInterpreterResult iresult = run_start_function(allocator, &thread); + if (iresult == WASM_INTERPRETER_OK) { + if (s_run_all_exports) + run_all_exports(allocator, module, &thread, RUN_VERBOSE); + } else { + print_interpreter_result("error running start function", iresult); + } } wasm_destroy_interpreter_thread(allocator, &thread); wasm_destroy_interpreter_environment(allocator, &env); return result; } -static WasmResult read_and_run_spec_json(WasmAllocator* allocator, - const char* spec_json_filename) { - WasmResult result = WASM_OK; +WASM_DEFINE_VECTOR(interpreter_thread, WasmInterpreterThread); + +/* An extremely simple JSON parser that only knows how to parse the expected + * format from wast2wasm. */ +typedef struct Context { + WasmAllocator* allocator; WasmInterpreterEnvironment env; - WasmInterpreterModule* module = NULL; - WasmInterpreterThread thread; - WasmStringSlice command_file; - WasmStringSlice command_name; - WasmInterpreterTypedValueVector result_values; - uint32_t command_line_no = 0; - WasmBool has_thread = WASM_FALSE; - uint32_t passed = 0; - uint32_t failed = 0; - - WASM_ZERO_MEMORY(thread); - WASM_ZERO_MEMORY(command_file); - WASM_ZERO_MEMORY(command_name); - WASM_ZERO_MEMORY(result_values); + WasmInterpreterThreadVector threads; + WasmInterpreterModule* last_module; + int thread_to_module_offset; + + /* Parsing info */ + char* json_data; + size_t json_data_size; + WasmStringSlice source_filename; + size_t json_offset; + WasmLocation loc; + WasmLocation prev_loc; + WasmBool has_prev_loc; + uint32_t command_line_number; + + /* Test info */ + int passed; + int total; +} Context; + +typedef enum ActionType { + ACTION_TYPE_INVOKE, + ACTION_TYPE_GET, +} ActionType; + +typedef struct Action { + ActionType type; + WasmStringSlice module_name; + WasmStringSlice field_name; + WasmInterpreterTypedValueVector args; +} Action; + +#define CHECK_RESULT(x) \ + do { \ + if (WASM_FAILED(x)) \ + return WASM_ERROR; \ + } while (0) + +#define EXPECT(x) CHECK_RESULT(expect(ctx, x)) +#define EXPECT_KEY(x) CHECK_RESULT(expect_key(ctx, x)) +#define PARSE_KEY_STRING_VALUE(key, value) \ + CHECK_RESULT(parse_key_string_value(ctx, key, value)) + +static void WASM_PRINTF_FORMAT(2, 3) + print_parse_error(Context* ctx, const char* format, ...) { + WASM_SNPRINTF_ALLOCA(buffer, length, format); + fprintf(stderr, "%s:%d:%d: %s\n", ctx->loc.filename, + ctx->loc.line, ctx->loc.first_column, buffer); +} - init_environment(allocator, &env); +static void WASM_PRINTF_FORMAT(2, 3) + print_command_error(Context* ctx, const char* format, ...) { + WASM_SNPRINTF_ALLOCA(buffer, length, format); + printf(PRIstringslice ":%u: %s\n", + WASM_PRINTF_STRING_SLICE_ARG(ctx->source_filename), + ctx->command_line_number, buffer); +} - void* data; - size_t size; - result = wasm_read_file(allocator, spec_json_filename, &data, &size); - if (WASM_FAILED(result)) - return WASM_ERROR; +static void putback_char(Context* ctx) { + assert(ctx->has_prev_loc); + ctx->json_offset--; + ctx->loc = ctx->prev_loc; + ctx->has_prev_loc = WASM_FALSE; +} - /* an extremely simple JSON parser that only knows how to parse the expected - * format from wast2wasm */ - enum { - INITIAL, - TOP_OBJECT, - MODULES_ARRAY, - END_MODULES_ARRAY, - MODULE_OBJECT, - END_MODULE_OBJECT, - MODULE_FILENAME, - COMMANDS_ARRAY, - END_COMMANDS_ARRAY, - COMMAND_OBJECT, - END_COMMAND_OBJECT, - COMMAND_TYPE, - COMMAND_NAME, - COMMAND_FILE, - COMMAND_LINE, - DONE, - } state = INITIAL; - - enum { - NONE, - ACTION, - ASSERT_RETURN, - ASSERT_RETURN_NAN, - ASSERT_TRAP, - } command_type = NONE; - -#define SKIP_WS() \ - while ((p < end) && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) \ - p++ - -#define MATCHES(c) ((p < end) && (*p == c) && (p++)) - -#define EXPECT(c) \ - if (!MATCHES(c)) \ - goto fail; - -#define STRLEN(s) (sizeof(s) - 1) - -#define MATCHES_STR(s) \ - ((p + STRLEN(s) < end) && (strncmp(p, s, STRLEN(s)) == 0) && (p += STRLEN(s))) - -#define EXPECT_STR(s) \ - if (!MATCHES_STR(s)) \ - goto fail; - -#define READ_UNTIL(c) \ - while (p < end && *p != c) \ - p++; \ - if (p == end) \ - goto fail - -#define READ_STRING(slice) \ - EXPECT('"'); \ - slice.start = p; \ - READ_UNTIL('"'); \ - slice.length = p - slice.start; \ - EXPECT('"') - -#define MAYBE_CONTINUE(name) \ - SKIP_WS(); \ - if (MATCHES(',')) \ - state = name; \ - else \ - state = END_##name - - const char* start = data; - const char* p = start; - const char* end = p + size; - while (p < end && state != DONE) { - SKIP_WS(); - - switch (state) { - case INITIAL: - EXPECT('{'); - state = TOP_OBJECT; - break; +static int read_char(Context* ctx) { + if (ctx->json_offset >= ctx->json_data_size) + return -1; + ctx->prev_loc = ctx->loc; + char c = ctx->json_data[ctx->json_offset++]; + if (c == '\n') { + ctx->loc.line++; + ctx->loc.first_column = 1; + } else { + ctx->loc.first_column++; + } + ctx->has_prev_loc = WASM_TRUE; + return c; +} - case TOP_OBJECT: - EXPECT_STR("\"modules\""); - SKIP_WS(); - EXPECT(':'); - SKIP_WS(); - EXPECT('['); - state = MODULES_ARRAY; - break; +static void skip_whitespace(Context* ctx) { + while (1) { + switch (read_char(ctx)) { + case -1: + return; - case MODULES_ARRAY: - if (MATCHES(']')) { - /* should only match with an empty module list */ - SKIP_WS(); - EXPECT('}'); - state = DONE; - } else { - EXPECT('{'); - state = MODULE_OBJECT; - } + case ' ': + case '\t': + case '\n': + case '\r': break; - case END_MODULES_ARRAY: - EXPECT(']'); - SKIP_WS(); - EXPECT('}'); - state = DONE; - break; + default: + putback_char(ctx); + return; + } + } +} - case MODULE_OBJECT: - if (MATCHES_STR("\"filename\"")) { - SKIP_WS(); - EXPECT(':'); - state = MODULE_FILENAME; - } else { - EXPECT_STR("\"commands\""); - SKIP_WS(); - EXPECT(':'); - SKIP_WS(); - EXPECT('['); - state = COMMANDS_ARRAY; - } - break; +static WasmBool match(Context* ctx, const char* s) { + skip_whitespace(ctx); + WasmLocation start_loc = ctx->loc; + size_t start_offset = ctx->json_offset; + while (*s && *s == read_char(ctx)) + s++; - case END_MODULE_OBJECT: - EXPECT('}'); - MAYBE_CONTINUE(MODULES_ARRAY); - assert(has_thread); - wasm_destroy_interpreter_thread(allocator, &thread); - has_thread = WASM_FALSE; - break; + if (*s == 0) { + return WASM_TRUE; + } else { + ctx->json_offset = start_offset; + ctx->loc = start_loc; + return WASM_FALSE; + } +} + +static WasmResult expect(Context* ctx, const char* s) { + if (match(ctx, s)) { + return WASM_OK; + } else { + print_parse_error(ctx, "expected %s", s); + return WASM_ERROR; + } +} + +static WasmResult expect_key(Context* ctx, const char* key) { + size_t keylen = strlen(key); + size_t quoted_len = keylen + 2 + 1; + char* quoted = alloca(quoted_len); + wasm_snprintf(quoted, quoted_len, "\"%s\"", key); + EXPECT(quoted); + EXPECT(":"); + return WASM_OK; +} + +static WasmResult parse_uint32(Context* ctx, uint32_t* out_int) { + uint32_t result = 0; + skip_whitespace(ctx); + while (1) { + int c = read_char(ctx); + if (c >= '0' && c <= '9') { + uint32_t last_result = result; + result = result * 10 + (uint32_t)(c - '0'); + if (result < last_result) { + print_parse_error(ctx, "uint32 overflow"); + return WASM_ERROR; + } + } else { + putback_char(ctx); + break; + } + } + *out_int = result; + return WASM_OK; +} - case MODULE_FILENAME: { - WasmStringSlice module_filename; - READ_STRING(module_filename); - WasmStringSlice dirname = get_dirname(spec_json_filename); - size_t path_len = dirname.length + 1 + module_filename.length + 1; - char* path = alloca(path_len); - if (dirname.length == 0) { - wasm_snprintf(path, path_len, PRIstringslice, - WASM_PRINTF_STRING_SLICE_ARG(module_filename)); +static WasmResult parse_string(Context* ctx, WasmStringSlice* out_string) { + skip_whitespace(ctx); + if (read_char(ctx) != '"') { + print_parse_error(ctx, "expected string"); + return WASM_ERROR; + } + /* Modify json_data in-place so we can use the WasmStringSlice directly + * without having to allocate additional memory; this is only necessary when + * the string contains an escape, but we do it always because the code is + * simpler. */ + char* start = &ctx->json_data[ctx->json_offset]; + char* p = start; + out_string->start = start; + while (1) { + int c = read_char(ctx); + if (c == '"') { + break; + } else if (c == '\\') { + /* The only escape supported is \uxxxx. */ + c = read_char(ctx); + if (c != 'u') { + print_parse_error(ctx, "expected escape: \\uxxxx"); + return WASM_ERROR; + } + int i; + uint16_t code = 0; + for (i = 0; i < 4; ++i) { + c = read_char(ctx); + int cval; + if (c >= '0' && c <= '9') { + cval = c - '0'; + } else if (c >= 'a' && c <= 'f') { + cval = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + cval = c - 'A' + 10; } else { - wasm_snprintf(path, path_len, PRIstringslice "/" PRIstringslice, - WASM_PRINTF_STRING_SLICE_ARG(dirname), - WASM_PRINTF_STRING_SLICE_ARG(module_filename)); + print_parse_error(ctx, "expected hex char"); + return WASM_ERROR; } + code = (code << 4) + cval; + } + + if (code < 256) { + *p++ = code; + } else { + print_parse_error(ctx, "only escape codes < 256 allowed, got %u\n", + code); + } + } else { + *p++ = c; + } + } + out_string->length = p - start; + return WASM_OK; +} + +static WasmResult parse_key_string_value(Context* ctx, + const char* key, + WasmStringSlice* out_string) { + EXPECT_KEY(key); + return parse_string(ctx, out_string); +} - result = read_module(allocator, path, &env, &module, &thread); - if (WASM_FAILED(result)) - goto fail; +static WasmResult parse_line(Context* ctx) { + EXPECT_KEY("line"); + CHECK_RESULT(parse_uint32(ctx, &ctx->command_line_number)); + return WASM_OK; +} - has_thread = WASM_TRUE; +static WasmBool string_slice_equals_str(const WasmStringSlice* ss, + const char* s) { + return strncmp(ss->start, s, ss->length) == 0; +} - result = run_start_function(&thread); - if (WASM_FAILED(result)) - goto fail; +static WasmResult parse_const(Context* ctx, + WasmInterpreterTypedValue* out_value) { + WasmStringSlice type_str; + WasmStringSlice value_str; + EXPECT("{"); + PARSE_KEY_STRING_VALUE("type", &type_str); + EXPECT(","); + PARSE_KEY_STRING_VALUE("value", &value_str); + EXPECT("}"); + + const char* value_start = value_str.start; + const char* value_end = value_str.start + value_str.length; + + if (string_slice_equals_str(&type_str, "i32")) { + uint32_t value; + CHECK_RESULT(wasm_parse_int32(value_start, value_end, &value, + WASM_PARSE_UNSIGNED_ONLY)); + out_value->type = WASM_TYPE_I32; + out_value->value.i32 = value; + return WASM_OK; + } else if (string_slice_equals_str(&type_str, "f32")) { + uint32_t value_bits; + CHECK_RESULT(wasm_parse_int32(value_start, value_end, &value_bits, + WASM_PARSE_UNSIGNED_ONLY)); + out_value->type = WASM_TYPE_F32; + out_value->value.f32_bits = value_bits; + return WASM_OK; + } else if (string_slice_equals_str(&type_str, "i64")) { + uint64_t value; + CHECK_RESULT(wasm_parse_int64(value_start, value_end, &value, + WASM_PARSE_UNSIGNED_ONLY)); + out_value->type = WASM_TYPE_I64; + out_value->value.i64 = value; + return WASM_OK; + } else if (string_slice_equals_str(&type_str, "f64")) { + uint64_t value_bits; + CHECK_RESULT(wasm_parse_int64(value_start, value_end, &value_bits, + WASM_PARSE_UNSIGNED_ONLY)); + out_value->type = WASM_TYPE_F64; + out_value->value.f64_bits = value_bits; + return WASM_OK; + } else { + print_parse_error(ctx, "unknown type: \"" PRIstringslice "\"", + WASM_PRINTF_STRING_SLICE_ARG(type_str)); + return WASM_ERROR; + } +} - MAYBE_CONTINUE(MODULE_OBJECT); - break; - } +static WasmResult parse_const_vector( + Context* ctx, + WasmInterpreterTypedValueVector* out_values) { + WASM_ZERO_MEMORY(*out_values); + EXPECT("["); + WasmBool first = WASM_TRUE; + while (!match(ctx, "]")) { + if (!first) + EXPECT(","); + WasmInterpreterTypedValue value; + CHECK_RESULT(parse_const(ctx, &value)); + wasm_append_interpreter_typed_value_value(ctx->allocator, out_values, + &value); + first = WASM_FALSE; + } + return WASM_OK; +} - case COMMANDS_ARRAY: - if (MATCHES(']')) { - /* should only match with an empty command array */ - state = END_MODULE_OBJECT; - } else { - EXPECT('{'); - state = COMMAND_OBJECT; - } - break; +static WasmResult parse_action(Context* ctx, Action* out_action) { + WASM_ZERO_MEMORY(*out_action); + EXPECT_KEY("action"); + EXPECT("{"); + EXPECT_KEY("type"); + if (match(ctx, "\"invoke\"")) { + out_action->type = ACTION_TYPE_INVOKE; + } else { + EXPECT("\"get\""); + out_action->type = ACTION_TYPE_GET; + } + EXPECT(","); + if (match(ctx, "\"module\"")) { + EXPECT(":"); + CHECK_RESULT(parse_string(ctx, &out_action->module_name)); + EXPECT(","); + } + PARSE_KEY_STRING_VALUE("field", &out_action->field_name); + if (out_action->type == ACTION_TYPE_INVOKE) { + EXPECT(","); + EXPECT_KEY("args"); + CHECK_RESULT(parse_const_vector(ctx, &out_action->args)); + } + EXPECT("}"); + return WASM_OK; +} - case END_COMMANDS_ARRAY: - EXPECT(']'); - MAYBE_CONTINUE(MODULE_OBJECT); - break; +static WasmResult on_module_command(Context* ctx, + WasmStringSlice filename, + WasmStringSlice name) { + const char* spec_json_filename = ctx->loc.filename; + WasmStringSlice dirname = get_dirname(spec_json_filename); + size_t path_len = dirname.length + 1 + filename.length + 1; + char* path = alloca(path_len); + + if (dirname.length == 0) { + wasm_snprintf(path, path_len, PRIstringslice, + WASM_PRINTF_STRING_SLICE_ARG(filename)); + } else { + wasm_snprintf(path, path_len, PRIstringslice "/" PRIstringslice, + WASM_PRINTF_STRING_SLICE_ARG(dirname), + WASM_PRINTF_STRING_SLICE_ARG(filename)); + } - case COMMAND_OBJECT: - if (MATCHES_STR("\"type\"")) { - state = COMMAND_TYPE; - } else if (MATCHES_STR("\"name\"")) { - state = COMMAND_NAME; - } else if (MATCHES_STR("\"file\"")) { - state = COMMAND_FILE; - } else { - EXPECT_STR("\"line\""); - state = COMMAND_LINE; - } - SKIP_WS(); - EXPECT(':'); - break; + /* Make sure that the difference in size between the thread and module + * vectors is constant; the only modules that don't have matching threads are + * host modules, which should all be added at the beginning. */ + if (ctx->threads.size == 0) { + ctx->thread_to_module_offset = (int)ctx->env.modules.size; + } else { + assert(ctx->thread_to_module_offset == + (int)(ctx->env.modules.size - ctx->threads.size)); + } -#define FAILED(msg) \ - fprintf(stderr, "*** " PRIstringslice ":%d: " PRIstringslice " " msg "\n", \ - WASM_PRINTF_STRING_SLICE_ARG(command_file), command_line_no, \ - WASM_PRINTF_STRING_SLICE_ARG(command_name)); - - case END_COMMAND_OBJECT: { - WasmInterpreterResult iresult; - EXPECT('}'); - RunVerbosity verbose = command_type == ACTION ? RUN_VERBOSE : RUN_QUIET; - result = run_export_by_name(allocator, &thread, &command_name, &iresult, - &result_values, verbose); - if (WASM_FAILED(result)) { - FAILED("unknown export"); - failed++; - goto fail; - } + WasmInterpreterThread* thread = + wasm_append_interpreter_thread(ctx->allocator, &ctx->threads); - switch (command_type) { - case ACTION: - if (iresult != WASM_INTERPRETER_OK) { - FAILED("trapped"); - failed++; - } - break; - - case ASSERT_RETURN: - case ASSERT_RETURN_NAN: - if (iresult != WASM_INTERPRETER_OK) { - FAILED("trapped"); - failed++; - } else if (result_values.size != 1) { - FAILED("result arity mismatch"); - failed++; - } else if (result_values.data[0].type != WASM_TYPE_I32) { - FAILED("type mismatch"); - failed++; - } else if (result_values.data[0].value.i32 != 1) { - FAILED("didn't return 1"); - failed++; - } else { - passed++; - } - break; - - case ASSERT_TRAP: - if (iresult == WASM_INTERPRETER_OK) { - FAILED("didn't trap"); - failed++; - } else { - passed++; - } - break; - - default: - assert(0); - goto fail; - } - MAYBE_CONTINUE(COMMANDS_ARRAY); - break; + CHECK_RESULT( + read_module(ctx->allocator, path, &ctx->env, &ctx->last_module, thread)); + + if (ctx->last_module->name.start) { + WasmBinding* binding = wasm_insert_binding( + ctx->allocator, &ctx->env.module_bindings, &ctx->last_module->name); + binding->index = ctx->env.modules.size - 1; + } + + WasmInterpreterResult iresult = run_start_function(ctx->allocator, thread); + if (iresult != WASM_INTERPRETER_OK) { + print_interpreter_result("error running start function", iresult); + return WASM_ERROR; + } + + return WASM_OK; +} + +static WasmResult run_action(Context* ctx, + Action* action, + WasmInterpreterResult* out_iresult, + WasmInterpreterTypedValueVector* out_results, + RunVerbosity verbose) { + WASM_ZERO_MEMORY(*out_results); + + int module_index; + if (action->module_name.start) { + module_index = wasm_find_binding_index_by_name(&ctx->env.module_bindings, + &action->module_name); + } else { + module_index = (int)ctx->env.modules.size - 1; + } + + int thread_index = module_index - ctx->thread_to_module_offset; + if (thread_index < 0 || thread_index >= (int)ctx->threads.size) { + print_command_error(ctx, "invalid module in action."); + return WASM_ERROR; + } + + WasmInterpreterThread* thread = &ctx->threads.data[thread_index]; + + switch (action->type) { + case ACTION_TYPE_INVOKE: + *out_iresult = + run_export_by_name(ctx->allocator, thread, &action->field_name, + &action->args, out_results, verbose); + if (verbose) { + print_call(wasm_empty_string_slice(), action->field_name, &action->args, + out_results, *out_iresult); } + return WASM_OK; - case COMMAND_TYPE: { - if (MATCHES_STR("\"action\"")) { - command_type = ACTION; - } else if (MATCHES_STR("\"assert_return\"")) { - command_type = ASSERT_RETURN; - } else if (MATCHES_STR("\"assert_return_nan\"")) { - command_type = ASSERT_RETURN_NAN; - } else { - EXPECT_STR("\"assert_trap\""); - command_type = ASSERT_TRAP; + default: + case ACTION_TYPE_GET: + return WASM_ERROR; + } +} + +static WasmResult on_action_command(Context* ctx, Action* action) { + WasmInterpreterTypedValueVector results; + WasmInterpreterResult iresult; + + ctx->total++; + WasmResult result = run_action(ctx, action, &iresult, &results, RUN_VERBOSE); + if (WASM_SUCCEEDED(result)) { + if (iresult == WASM_INTERPRETER_OK) { + ctx->passed++; + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WASM_ERROR; + } + } + + wasm_destroy_interpreter_typed_value_vector(ctx->allocator, &results); + return result; +} + +static WasmResult on_assert_malformed_command(Context* ctx, + WasmStringSlice filename, + WasmStringSlice text) { + /* TODO */ + return WASM_OK; +} + +static WasmResult on_register_command(Context* ctx, + WasmStringSlice name, + WasmStringSlice as) { + /* TODO */ + return WASM_OK; +} + +static WasmResult on_assert_unlinkable_command(Context* ctx, + WasmStringSlice filename, + WasmStringSlice text) { + /* TODO */ + return WASM_OK; +} + +static WasmBool typed_values_are_equal(const WasmInterpreterTypedValue* tv1, + const WasmInterpreterTypedValue* tv2) { + if (tv1->type != tv2->type) + return WASM_FALSE; + + switch (tv1->type) { + case WASM_TYPE_I32: return tv1->value.i32 == tv2->value.i32; + case WASM_TYPE_F32: return tv1->value.f32_bits == tv2->value.f32_bits; + case WASM_TYPE_I64: return tv1->value.i64 == tv2->value.i64; + case WASM_TYPE_F64: return tv1->value.f64_bits == tv2->value.f64_bits; + default: assert(0); return WASM_FALSE; + } +} + +static WasmResult on_assert_return_command( + Context* ctx, + Action* action, + WasmInterpreterTypedValueVector* expected) { + WasmInterpreterTypedValueVector results; + WasmInterpreterResult iresult; + + ctx->total++; + WasmResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + + if (WASM_SUCCEEDED(result)) { + if (iresult == WASM_INTERPRETER_OK) { + if (results.size == expected->size) { + size_t i; + for (i = 0; i < results.size; ++i) { + const WasmInterpreterTypedValue* expected_tv = &expected->data[i]; + const WasmInterpreterTypedValue* actual_tv = &results.data[i]; + if (!typed_values_are_equal(expected_tv, actual_tv)) { + char expected_str[MAX_TYPED_VALUE_CHARS]; + char actual_str[MAX_TYPED_VALUE_CHARS]; + sprint_typed_value(expected_str, sizeof(expected_str), expected_tv); + sprint_typed_value(actual_str, sizeof(actual_str), actual_tv); + print_command_error(ctx, "mismatch in result %" PRIzd + " of assert_return: expected %s, got %s", + i, expected_str, actual_str); + result = WASM_ERROR; + } } - MAYBE_CONTINUE(COMMAND_OBJECT); - break; + } else { + print_command_error( + ctx, "result length mismatch in assert_return: expected %" PRIzd + ", got %" PRIzd, + expected->size, results.size); + result = WASM_ERROR; } + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WASM_ERROR; + } + } - case COMMAND_NAME: { - READ_STRING(command_name); - MAYBE_CONTINUE(COMMAND_OBJECT); - break; - } + if (WASM_SUCCEEDED(result)) + ctx->passed++; - case COMMAND_FILE: { - READ_STRING(command_file); - MAYBE_CONTINUE(COMMAND_OBJECT); - break; + wasm_destroy_interpreter_typed_value_vector(ctx->allocator, &results); + return result; +} + +static WasmResult on_assert_return_nan_command(Context* ctx, Action* action) { + WasmInterpreterTypedValueVector results; + WasmInterpreterResult iresult; + + ctx->total++; + WasmResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + if (WASM_SUCCEEDED(result)) { + if (iresult == WASM_INTERPRETER_OK) { + if (results.size != 1) { + print_command_error(ctx, "expected one result, got %" PRIzd, + results.size); + result = WASM_ERROR; } - case COMMAND_LINE: { - command_line_no = 0; - while (p < end && *p >= '0' && *p <= '9') { - uint32_t new_line_no = command_line_no * 10 + (*p - '0'); - if (new_line_no < command_line_no) - goto fail; - command_line_no = new_line_no; - p++; - } - MAYBE_CONTINUE(COMMAND_OBJECT); - break; + const WasmInterpreterTypedValue* actual = &results.data[0]; + switch (actual->type) { + case WASM_TYPE_F32: + if (!is_nan_f32(actual->value.f32_bits)) { + char actual_str[MAX_TYPED_VALUE_CHARS]; + sprint_typed_value(actual_str, sizeof(actual_str), actual); + print_command_error(ctx, "expected result to be nan, got %s", + actual_str); + result = WASM_ERROR; + } + break; + + case WASM_TYPE_F64: + if (!is_nan_f64(actual->value.f64_bits)) { + char actual_str[MAX_TYPED_VALUE_CHARS]; + sprint_typed_value(actual_str, sizeof(actual_str), actual); + print_command_error(ctx, "expected result to be nan, got %s", + actual_str); + result = WASM_ERROR; + } + break; + + default: + print_command_error(ctx, + "expected result type to be f32 or f64, got %s", + wasm_get_type_name(actual->type)); + result = WASM_ERROR; + break; } - default: - assert(0); - break; + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WASM_ERROR; } } - uint32_t total = passed + failed; - printf("%d/%d tests passed.\n", passed, total); - result = passed == total ? WASM_OK : WASM_ERROR; + if (WASM_SUCCEEDED(result)) + ctx->passed++; - goto done; + wasm_destroy_interpreter_typed_value_vector(ctx->allocator, &results); + return WASM_OK; +} -fail: - fprintf(stderr, "error parsing spec json file\n"); - fprintf(stderr, "got this far: %" PRIzd ":> %.*s...\n", p - start, 20, p); - result = WASM_ERROR; +static WasmResult on_assert_trap_command(Context* ctx, + Action* action, + WasmStringSlice text) { + WasmInterpreterTypedValueVector results; + WasmInterpreterResult iresult; -done: - if (has_thread) - wasm_destroy_interpreter_thread(allocator, &thread); - wasm_destroy_interpreter_environment(allocator, &env); - wasm_destroy_interpreter_typed_value_vector(allocator, &result_values); - wasm_free(allocator, data); + ctx->total++; + WasmResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + if (WASM_SUCCEEDED(result)) { + if (iresult != WASM_INTERPRETER_OK) { + ctx->passed++; + } else { + print_command_error(ctx, "expected trap: \"" PRIstringslice "\"", + WASM_PRINTF_STRING_SLICE_ARG(text)); + result = WASM_ERROR; + } + } + + wasm_destroy_interpreter_typed_value_vector(ctx->allocator, &results); + return result; +} + +static void destroy_action(WasmAllocator* allocator, Action* action) { + wasm_destroy_interpreter_typed_value_vector(allocator, &action->args); +} + +static WasmResult parse_command(Context* ctx) { + EXPECT("{"); + EXPECT_KEY("type"); + if (match(ctx, "\"module\"")) { + WasmStringSlice name; + WasmStringSlice filename; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + if (match(ctx, "\"name\"")) { + EXPECT(":"); + CHECK_RESULT(parse_string(ctx, &name)); + EXPECT(","); + } + PARSE_KEY_STRING_VALUE("filename", &filename); + on_module_command(ctx, filename, name); + } else if (match(ctx, "\"action\"")) { + Action action; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + on_action_command(ctx, &action); + destroy_action(ctx->allocator, &action); + } else if (match(ctx, "\"register\"")) { + WasmStringSlice as; + WasmStringSlice name; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("name", &name); + EXPECT(","); + PARSE_KEY_STRING_VALUE("as", &as); + on_register_command(ctx, name, as); + } else if (match(ctx, "\"assert_malformed\"")) { + WasmStringSlice filename; + WasmStringSlice text; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + on_assert_malformed_command(ctx, filename, text); + } else if (match(ctx, "\"assert_invalid\"")) { + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); +#if 0 + /* TODO(binji): this doesn't work currently because the spec writer can't + * write invalid modules in all cases. */ + + WasmStringSlice filename; + WasmStringSlice text; + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); +#endif + } else if (match(ctx, "\"assert_unlinkable\"")) { + WasmStringSlice filename; + WasmStringSlice text; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + on_assert_unlinkable_command(ctx, filename, text); + } else if (match(ctx, "\"assert_return\"")) { + Action action; + WasmInterpreterTypedValueVector expected; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + EXPECT(","); + EXPECT_KEY("expected"); + CHECK_RESULT(parse_const_vector(ctx, &expected)); + on_assert_return_command(ctx, &action, &expected); + wasm_destroy_interpreter_typed_value_vector(ctx->allocator, &expected); + destroy_action(ctx->allocator, &action); + } else if (match(ctx, "\"assert_return_nan\"")) { + Action action; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + on_assert_return_nan_command(ctx, &action); + destroy_action(ctx->allocator, &action); + } else if (match(ctx, "\"assert_trap\"")) { + Action action; + WasmStringSlice text; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + on_assert_trap_command(ctx, &action, text); + destroy_action(ctx->allocator, &action); + } else { + print_command_error(ctx, "unknown command type"); + return WASM_ERROR; + } + EXPECT("}"); + return WASM_OK; +} + +static WasmResult parse_commands(Context* ctx) { + EXPECT("{"); + PARSE_KEY_STRING_VALUE("source_filename", &ctx->source_filename); + EXPECT(","); + EXPECT_KEY("commands"); + EXPECT("["); + WasmBool first = WASM_TRUE; + while (!match(ctx, "]")) { + if (!first) + EXPECT(","); + CHECK_RESULT(parse_command(ctx)); + first = WASM_FALSE; + } + EXPECT("}"); + return WASM_OK; +} + +static void destroy_context(Context* ctx) { + WASM_DESTROY_VECTOR_AND_ELEMENTS(ctx->allocator, ctx->threads, + interpreter_thread); + wasm_destroy_interpreter_environment(ctx->allocator, &ctx->env); + wasm_free(ctx->allocator, ctx->json_data); +} + +static WasmResult read_and_run_spec_json(WasmAllocator* allocator, + const char* spec_json_filename) { + Context ctx; + WASM_ZERO_MEMORY(ctx); + ctx.allocator = allocator; + ctx.loc.filename = spec_json_filename; + ctx.loc.line = 1; + ctx.loc.first_column = 1; + init_environment(allocator, &ctx.env); + + void* data; + size_t size; + WasmResult result = + wasm_read_file(allocator, spec_json_filename, &data, &size); + if (WASM_FAILED(result)) + return WASM_ERROR; + + ctx.json_data = data; + ctx.json_data_size = size; + + result = parse_commands(&ctx); + printf("%d/%d tests passed.\n", ctx.passed, ctx.total); + destroy_context(&ctx); return result; } |