diff options
Diffstat (limited to 'src/tools/wasm-interp.cc')
-rw-r--r-- | src/tools/wasm-interp.cc | 1676 |
1 files changed, 1676 insertions, 0 deletions
diff --git a/src/tools/wasm-interp.cc b/src/tools/wasm-interp.cc new file mode 100644 index 00000000..76fd901f --- /dev/null +++ b/src/tools/wasm-interp.cc @@ -0,0 +1,1676 @@ +/* + * 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 <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> + +#include "binary-reader.h" +#include "binary-reader-interpreter.h" +#include "interpreter.h" +#include "literal.h" +#include "option-parser.h" +#include "stream.h" + +#define INSTRUCTION_QUANTUM 1000 +#define PROGRAM_NAME "wasm-interp" + +#define V(name, str) str, +static const char* s_trap_strings[] = {FOREACH_INTERPRETER_RESULT(V)}; +#undef V + +static int s_verbose; +static const char* s_infile; +static WabtReadBinaryOptions s_read_binary_options = + WABT_READ_BINARY_OPTIONS_DEFAULT; +static WabtInterpreterThreadOptions s_thread_options = + WABT_INTERPRETER_THREAD_OPTIONS_DEFAULT; +static bool s_trace; +static bool s_spec; +static bool s_run_all_exports; +static WabtStream* s_stdout_stream; + +static WabtBinaryErrorHandler s_error_handler = + WABT_BINARY_ERROR_HANDLER_DEFAULT; + +static WabtFileWriter s_log_stream_writer; +static WabtStream s_log_stream; + +#define NOPE WABT_OPTION_NO_ARGUMENT +#define YEP WABT_OPTION_HAS_ARGUMENT + +typedef enum RunVerbosity { + RUN_QUIET = 0, + RUN_VERBOSE = 1, +} RunVerbosity; + +enum { + FLAG_VERBOSE, + FLAG_HELP, + FLAG_VALUE_STACK_SIZE, + FLAG_CALL_STACK_SIZE, + FLAG_TRACE, + FLAG_SPEC, + FLAG_RUN_ALL_EXPORTS, + NUM_FLAGS +}; + +static const char s_description[] = + " read a file in the wasm binary format, and run in it a stack-based\n" + " interpreter.\n" + "\n" + "examples:\n" + " # parse binary file test.wasm, and type-check it\n" + " $ wasm-interp test.wasm\n" + "\n" + " # parse test.wasm and run all its exported functions\n" + " $ wasm-interp test.wasm --run-all-exports\n" + "\n" + " # parse test.wasm, run the exported functions and trace the output\n" + " $ wasm-interp test.wasm --run-all-exports --trace\n" + "\n" + " # parse test.json and run the spec tests\n" + " $ wasm-interp test.json --spec\n" + "\n" + " # parse test.wasm and run all its exported functions, setting the\n" + " # value stack size to 100 elements\n" + " $ wasm-interp test.wasm -V 100 --run-all-exports\n"; + +static WabtOption s_options[] = { + {FLAG_VERBOSE, 'v', "verbose", NULL, NOPE, + "use multiple times for more info"}, + {FLAG_HELP, 'h', "help", NULL, NOPE, "print this help message"}, + {FLAG_VALUE_STACK_SIZE, 'V', "value-stack-size", "SIZE", YEP, + "size in elements of the value stack"}, + {FLAG_CALL_STACK_SIZE, 'C', "call-stack-size", "SIZE", YEP, + "size in frames of the call stack"}, + {FLAG_TRACE, 't', "trace", NULL, NOPE, "trace execution"}, + {FLAG_SPEC, 0, "spec", NULL, NOPE, + "run spec tests (input file should be .json)"}, + {FLAG_RUN_ALL_EXPORTS, 0, "run-all-exports", NULL, NOPE, + "run all the exported functions, in order. useful for testing"}, +}; +WABT_STATIC_ASSERT(NUM_FLAGS == WABT_ARRAY_SIZE(s_options)); + +static void on_option(struct WabtOptionParser* parser, + struct WabtOption* option, + const char* argument) { + switch (option->id) { + case FLAG_VERBOSE: + s_verbose++; + wabt_init_file_writer_existing(&s_log_stream_writer, stdout); + wabt_init_stream(&s_log_stream, &s_log_stream_writer.base, NULL); + s_read_binary_options.log_stream = &s_log_stream; + break; + + case FLAG_HELP: + wabt_print_help(parser, PROGRAM_NAME); + exit(0); + break; + + case FLAG_VALUE_STACK_SIZE: + /* TODO(binji): validate */ + s_thread_options.value_stack_size = atoi(argument); + break; + + case FLAG_CALL_STACK_SIZE: + /* TODO(binji): validate */ + s_thread_options.call_stack_size = atoi(argument); + break; + + case FLAG_TRACE: + s_trace = true; + break; + + case FLAG_SPEC: + s_spec = true; + break; + + case FLAG_RUN_ALL_EXPORTS: + s_run_all_exports = true; + break; + } +} + +static void on_argument(struct WabtOptionParser* parser, const char* argument) { + s_infile = argument; +} + +static void on_option_error(struct WabtOptionParser* parser, + const char* message) { + WABT_FATAL("%s\n", message); +} + +static void parse_options(int argc, char** argv) { + WabtOptionParser parser; + WABT_ZERO_MEMORY(parser); + parser.description = s_description; + parser.options = s_options; + parser.num_options = WABT_ARRAY_SIZE(s_options); + parser.on_option = on_option; + parser.on_argument = on_argument; + parser.on_error = on_option_error; + wabt_parse_options(&parser, argc, argv); + + if (s_spec && s_run_all_exports) + WABT_FATAL("--spec and --run-all-exports are incompatible.\n"); + + if (!s_infile) { + wabt_print_help(&parser, PROGRAM_NAME); + WABT_FATAL("No filename given.\n"); + } +} + +static WabtStringSlice get_dirname(const char* s) { + /* strip everything after and including the last slash, e.g.: + * + * s = "foo/bar/baz", => "foo/bar" + * s = "/usr/local/include/stdio.h", => "/usr/local/include" + * s = "foo.bar", => "" + */ + const char* last_slash = strrchr(s, '/'); + if (last_slash == NULL) + last_slash = s; + + WabtStringSlice result; + result.start = s; + result.length = last_slash - s; + return result; +} + +/* 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 WabtInterpreterTypedValue* tv) { + switch (tv->type) { + case WABT_TYPE_I32: + wabt_snprintf(buffer, size, "i32:%u", tv->value.i32); + break; + + case WABT_TYPE_I64: + wabt_snprintf(buffer, size, "i64:%" PRIu64, tv->value.i64); + break; + + case WABT_TYPE_F32: { + float value; + memcpy(&value, &tv->value.f32_bits, sizeof(float)); + wabt_snprintf(buffer, size, "f32:%g", value); + break; + } + + case WABT_TYPE_F64: { + double value; + memcpy(&value, &tv->value.f64_bits, sizeof(double)); + wabt_snprintf(buffer, size, "f64:%g", value); + break; + } + + default: + assert(0); + break; + } +} + +static void print_typed_value(const WabtInterpreterTypedValue* tv) { + char buffer[MAX_TYPED_VALUE_CHARS]; + sprint_typed_value(buffer, sizeof(buffer), tv); + printf("%s", buffer); +} + +static void print_typed_values(const WabtInterpreterTypedValue* 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 WabtInterpreterTypedValueVector* values) { + print_typed_values(&values->data[0], values->size); +} + +static void print_interpreter_result(const char* desc, + WabtInterpreterResult iresult) { + printf("%s: %s\n", desc, s_trap_strings[iresult]); +} + +static void print_call(WabtStringSlice module_name, + WabtStringSlice func_name, + const WabtInterpreterTypedValueVector* args, + const WabtInterpreterTypedValueVector* results, + WabtInterpreterResult iresult) { + if (module_name.length) + printf(PRIstringslice ".", WABT_PRINTF_STRING_SLICE_ARG(module_name)); + printf(PRIstringslice "(", WABT_PRINTF_STRING_SLICE_ARG(func_name)); + print_typed_value_vector(args); + printf(") =>"); + if (iresult == WABT_INTERPRETER_OK) { + if (results->size > 0) { + printf(" "); + print_typed_value_vector(results); + } + printf("\n"); + } else { + print_interpreter_result(" error", iresult); + } +} + +static WabtInterpreterResult run_defined_function(WabtInterpreterThread* thread, + uint32_t offset) { + thread->pc = offset; + WabtInterpreterResult iresult = WABT_INTERPRETER_OK; + uint32_t quantum = s_trace ? 1 : INSTRUCTION_QUANTUM; + uint32_t* call_stack_return_top = thread->call_stack_top; + while (iresult == WABT_INTERPRETER_OK) { + if (s_trace) + wabt_trace_pc(thread, s_stdout_stream); + iresult = wabt_run_interpreter(thread, quantum, call_stack_return_top); + } + if (iresult != WABT_INTERPRETER_RETURNED) + return iresult; + /* use OK instead of RETURNED for consistency */ + return WABT_INTERPRETER_OK; +} + +static WabtInterpreterResult push_args( + WabtInterpreterThread* thread, + const WabtInterpreterFuncSignature* sig, + const WabtInterpreterTypedValueVector* args) { + if (sig->param_types.size != args->size) + return WABT_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 WABT_INTERPRETER_ARGUMENT_TYPE_MISMATCH; + + WabtInterpreterResult iresult = + wabt_push_thread_value(thread, args->data[i].value); + if (iresult != WABT_INTERPRETER_OK) { + thread->value_stack_top = thread->value_stack.data; + return iresult; + } + } + return WABT_INTERPRETER_OK; +} + +static void copy_results(WabtInterpreterThread* thread, + const WabtInterpreterFuncSignature* sig, + WabtInterpreterTypedValueVector* out_results) { + size_t expected_results = sig->result_types.size; + size_t value_stack_depth = thread->value_stack_top - thread->value_stack.data; + WABT_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; + wabt_resize_interpreter_typed_value_vector(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 WabtInterpreterResult run_function( + WabtInterpreterThread* thread, + uint32_t func_index, + const WabtInterpreterTypedValueVector* args, + WabtInterpreterTypedValueVector* out_results) { + assert(func_index < thread->env->funcs.size); + WabtInterpreterFunc* func = &thread->env->funcs.data[func_index]; + uint32_t sig_index = func->sig_index; + assert(sig_index < thread->env->sigs.size); + WabtInterpreterFuncSignature* sig = &thread->env->sigs.data[sig_index]; + + WabtInterpreterResult iresult = push_args(thread, sig, args); + if (iresult == WABT_INTERPRETER_OK) { + iresult = func->is_host + ? wabt_call_host(thread, func) + : run_defined_function(thread, func->defined.offset); + if (iresult == WABT_INTERPRETER_OK) + copy_results(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; +} + +static WabtInterpreterResult run_start_function(WabtInterpreterThread* thread, + WabtInterpreterModule* module) { + if (module->defined.start_func_index == WABT_INVALID_INDEX) + return WABT_INTERPRETER_OK; + + if (s_trace) + printf(">>> running start function:\n"); + WabtInterpreterTypedValueVector args; + WabtInterpreterTypedValueVector results; + WABT_ZERO_MEMORY(args); + WABT_ZERO_MEMORY(results); + + WabtInterpreterResult iresult = + run_function(thread, module->defined.start_func_index, &args, &results); + assert(results.size == 0); + return iresult; +} + +static WabtInterpreterResult run_export( + WabtInterpreterThread* thread, + const WabtInterpreterExport* export_, + const WabtInterpreterTypedValueVector* args, + WabtInterpreterTypedValueVector* out_results) { + if (s_trace) { + printf(">>> running export \"" PRIstringslice "\":\n", + WABT_PRINTF_STRING_SLICE_ARG(export_->name)); + } + + assert(export_->kind == WABT_EXTERNAL_KIND_FUNC); + return run_function(thread, export_->index, args, out_results); +} + +static WabtInterpreterResult run_export_by_name( + WabtInterpreterThread* thread, + WabtInterpreterModule* module, + const WabtStringSlice* name, + const WabtInterpreterTypedValueVector* args, + WabtInterpreterTypedValueVector* out_results, + RunVerbosity verbose) { + WabtInterpreterExport* export_ = + wabt_get_interpreter_export_by_name(module, name); + if (!export_) + return WABT_INTERPRETER_UNKNOWN_EXPORT; + if (export_->kind != WABT_EXTERNAL_KIND_FUNC) + return WABT_INTERPRETER_EXPORT_KIND_MISMATCH; + return run_export(thread, export_, args, out_results); +} + +static WabtInterpreterResult get_global_export_by_name( + WabtInterpreterThread* thread, + WabtInterpreterModule* module, + const WabtStringSlice* name, + WabtInterpreterTypedValueVector* out_results) { + WabtInterpreterExport* export_ = + wabt_get_interpreter_export_by_name(module, name); + if (!export_) + return WABT_INTERPRETER_UNKNOWN_EXPORT; + if (export_->kind != WABT_EXTERNAL_KIND_GLOBAL) + return WABT_INTERPRETER_EXPORT_KIND_MISMATCH; + + assert(export_->index < thread->env->globals.size); + WabtInterpreterGlobal* global = &thread->env->globals.data[export_->index]; + + /* Don't clear out the vector, in case it is being reused. Just reset the + * size to zero. */ + out_results->size = 0; + wabt_append_interpreter_typed_value_value(out_results, &global->typed_value); + return WABT_INTERPRETER_OK; +} + +static void run_all_exports(WabtInterpreterModule* module, + WabtInterpreterThread* thread, + RunVerbosity verbose) { + WabtInterpreterTypedValueVector args; + WabtInterpreterTypedValueVector results; + WABT_ZERO_MEMORY(args); + WABT_ZERO_MEMORY(results); + uint32_t i; + for (i = 0; i < module->exports.size; ++i) { + WabtInterpreterExport* export_ = &module->exports.data[i]; + WabtInterpreterResult iresult = + run_export(thread, export_, &args, &results); + if (verbose) { + print_call(wabt_empty_string_slice(), export_->name, &args, &results, + iresult); + } + } + wabt_destroy_interpreter_typed_value_vector(&args); + wabt_destroy_interpreter_typed_value_vector(&results); +} + +static WabtResult read_module(const char* module_filename, + WabtInterpreterEnvironment* env, + WabtBinaryErrorHandler* error_handler, + WabtInterpreterModule** out_module) { + WabtResult result; + void* data; + size_t size; + + *out_module = NULL; + + result = wabt_read_file(module_filename, &data, &size); + if (WABT_SUCCEEDED(result)) { + result = wabt_read_binary_interpreter( + env, data, size, &s_read_binary_options, error_handler, out_module); + + if (WABT_SUCCEEDED(result)) { + if (s_verbose) + wabt_disassemble_module(env, s_stdout_stream, *out_module); + } + wabt_free(data); + } + return result; +} + +static WabtResult default_host_callback(const WabtInterpreterFunc* func, + const WabtInterpreterFuncSignature* sig, + uint32_t num_args, + WabtInterpreterTypedValue* args, + uint32_t num_results, + WabtInterpreterTypedValue* out_results, + void* user_data) { + memset(out_results, 0, sizeof(WabtInterpreterTypedValue) * num_results); + uint32_t i; + for (i = 0; i < num_results; ++i) + out_results[i].type = sig->result_types.data[i]; + + WabtInterpreterTypedValueVector vec_args; + vec_args.size = num_args; + vec_args.data = args; + + WabtInterpreterTypedValueVector 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, WABT_INTERPRETER_OK); + return WABT_OK; +} + +#define PRIimport "\"" PRIstringslice "." PRIstringslice "\"" +#define PRINTF_IMPORT_ARG(x) \ + WABT_PRINTF_STRING_SLICE_ARG((x).module_name) \ + , WABT_PRINTF_STRING_SLICE_ARG((x).field_name) + +static void WABT_PRINTF_FORMAT(2, 3) + print_error(WabtPrintErrorCallback callback, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + callback.print_error(buffer, callback.user_data); +} + +static WabtResult spectest_import_func(WabtInterpreterImport* import, + WabtInterpreterFunc* func, + WabtInterpreterFuncSignature* sig, + WabtPrintErrorCallback callback, + void* user_data) { + if (wabt_string_slice_eq_cstr(&import->field_name, "print")) { + func->host.callback = default_host_callback; + return WABT_OK; + } else { + print_error(callback, "unknown host function import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return WABT_ERROR; + } +} + +static WabtResult spectest_import_table(WabtInterpreterImport* import, + WabtInterpreterTable* table, + WabtPrintErrorCallback callback, + void* user_data) { + if (wabt_string_slice_eq_cstr(&import->field_name, "table")) { + table->limits.has_max = true; + table->limits.initial = 10; + table->limits.max = 20; + return WABT_OK; + } else { + print_error(callback, "unknown host table import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return WABT_ERROR; + } +} + +static WabtResult spectest_import_memory(WabtInterpreterImport* import, + WabtInterpreterMemory* memory, + WabtPrintErrorCallback callback, + void* user_data) { + if (wabt_string_slice_eq_cstr(&import->field_name, "memory")) { + memory->page_limits.has_max = true; + memory->page_limits.initial = 1; + memory->page_limits.max = 2; + memory->byte_size = memory->page_limits.initial * WABT_MAX_PAGES; + memory->data = wabt_alloc_zero(memory->byte_size); + return WABT_OK; + } else { + print_error(callback, "unknown host memory import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return WABT_ERROR; + } +} + +static WabtResult spectest_import_global(WabtInterpreterImport* import, + WabtInterpreterGlobal* global, + WabtPrintErrorCallback callback, + void* user_data) { + if (wabt_string_slice_eq_cstr(&import->field_name, "global")) { + switch (global->typed_value.type) { + case WABT_TYPE_I32: + global->typed_value.value.i32 = 666; + break; + + case WABT_TYPE_F32: { + float value = 666.6f; + memcpy(&global->typed_value.value.f32_bits, &value, sizeof(value)); + break; + } + + case WABT_TYPE_I64: + global->typed_value.value.i64 = 666; + break; + + case WABT_TYPE_F64: { + double value = 666.6; + memcpy(&global->typed_value.value.f64_bits, &value, sizeof(value)); + break; + } + + default: + print_error(callback, "bad type for host global import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return WABT_ERROR; + } + + return WABT_OK; + } else { + print_error(callback, "unknown host global import " PRIimport, + PRINTF_IMPORT_ARG(*import)); + return WABT_ERROR; + } +} + +static void init_environment(WabtInterpreterEnvironment* env) { + wabt_init_interpreter_environment(env); + WabtInterpreterModule* host_module = + wabt_append_host_module(env, wabt_string_slice_from_cstr("spectest")); + host_module->host.import_delegate.import_func = spectest_import_func; + host_module->host.import_delegate.import_table = spectest_import_table; + host_module->host.import_delegate.import_memory = spectest_import_memory; + host_module->host.import_delegate.import_global = spectest_import_global; +} + +static WabtResult read_and_run_module(const char* module_filename) { + WabtResult result; + WabtInterpreterEnvironment env; + WabtInterpreterModule* module = NULL; + WabtInterpreterThread thread; + + init_environment(&env); + wabt_init_interpreter_thread(&env, &thread, &s_thread_options); + result = read_module(module_filename, &env, &s_error_handler, &module); + if (WABT_SUCCEEDED(result)) { + WabtInterpreterResult iresult = run_start_function(&thread, module); + if (iresult == WABT_INTERPRETER_OK) { + if (s_run_all_exports) + run_all_exports(module, &thread, RUN_VERBOSE); + } else { + print_interpreter_result("error running start function", iresult); + } + } + wabt_destroy_interpreter_thread(&thread); + wabt_destroy_interpreter_environment(&env); + return result; +} + +WABT_DEFINE_VECTOR(interpreter_thread, WabtInterpreterThread); + +/* An extremely simple JSON parser that only knows how to parse the expected + * format from wast2wabt. */ +typedef struct Context { + WabtInterpreterEnvironment env; + WabtInterpreterThread thread; + WabtInterpreterModule* last_module; + + /* Parsing info */ + char* json_data; + size_t json_data_size; + WabtStringSlice source_filename; + size_t json_offset; + WabtLocation loc; + WabtLocation prev_loc; + bool 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; + WabtStringSlice module_name; + WabtStringSlice field_name; + WabtInterpreterTypedValueVector args; +} Action; + +#define CHECK_RESULT(x) \ + do { \ + if (WABT_FAILED(x)) \ + return WABT_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 WABT_PRINTF_FORMAT(2, 3) + print_parse_error(Context* ctx, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + fprintf(stderr, "%s:%d:%d: %s\n", ctx->loc.filename, ctx->loc.line, + ctx->loc.first_column, buffer); +} + +static void WABT_PRINTF_FORMAT(2, 3) + print_command_error(Context* ctx, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + printf(PRIstringslice ":%u: %s\n", + WABT_PRINTF_STRING_SLICE_ARG(ctx->source_filename), + ctx->command_line_number, buffer); +} + +static void putback_char(Context* ctx) { + assert(ctx->has_prev_loc); + ctx->json_offset--; + ctx->loc = ctx->prev_loc; + ctx->has_prev_loc = false; +} + +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 = true; + return c; +} + +static void skip_whitespace(Context* ctx) { + while (1) { + switch (read_char(ctx)) { + case -1: + return; + + case ' ': + case '\t': + case '\n': + case '\r': + break; + + default: + putback_char(ctx); + return; + } + } +} + +static bool match(Context* ctx, const char* s) { + skip_whitespace(ctx); + WabtLocation start_loc = ctx->loc; + size_t start_offset = ctx->json_offset; + while (*s && *s == read_char(ctx)) + s++; + + if (*s == 0) { + return true; + } else { + ctx->json_offset = start_offset; + ctx->loc = start_loc; + return false; + } +} + +static WabtResult expect(Context* ctx, const char* s) { + if (match(ctx, s)) { + return WABT_OK; + } else { + print_parse_error(ctx, "expected %s", s); + return WABT_ERROR; + } +} + +static WabtResult expect_key(Context* ctx, const char* key) { + size_t keylen = strlen(key); + size_t quoted_len = keylen + 2 + 1; + char* quoted = (char*)alloca(quoted_len); + wabt_snprintf(quoted, quoted_len, "\"%s\"", key); + EXPECT(quoted); + EXPECT(":"); + return WABT_OK; +} + +static WabtResult 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 WABT_ERROR; + } + } else { + putback_char(ctx); + break; + } + } + *out_int = result; + return WABT_OK; +} + +static WabtResult parse_string(Context* ctx, WabtStringSlice* out_string) { + WABT_ZERO_MEMORY(*out_string); + + skip_whitespace(ctx); + if (read_char(ctx) != '"') { + print_parse_error(ctx, "expected string"); + return WABT_ERROR; + } + /* Modify json_data in-place so we can use the WabtStringSlice 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 WABT_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 { + print_parse_error(ctx, "expected hex char"); + return WABT_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 WABT_OK; +} + +static WabtResult parse_key_string_value(Context* ctx, + const char* key, + WabtStringSlice* out_string) { + WABT_ZERO_MEMORY(*out_string); + EXPECT_KEY(key); + return parse_string(ctx, out_string); +} + +static WabtResult parse_opt_name_string_value(Context* ctx, + WabtStringSlice* out_string) { + WABT_ZERO_MEMORY(*out_string); + if (match(ctx, "\"name\"")) { + EXPECT(":"); + CHECK_RESULT(parse_string(ctx, out_string)); + EXPECT(","); + } + return WABT_OK; +} + +static WabtResult parse_line(Context* ctx) { + EXPECT_KEY("line"); + CHECK_RESULT(parse_uint32(ctx, &ctx->command_line_number)); + return WABT_OK; +} + +static WabtResult parse_type_object(Context* ctx, WabtType* out_type) { + WabtStringSlice type_str; + EXPECT("{"); + PARSE_KEY_STRING_VALUE("type", &type_str); + EXPECT("}"); + + if (wabt_string_slice_eq_cstr(&type_str, "i32")) { + *out_type = WABT_TYPE_I32; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "f32")) { + *out_type = WABT_TYPE_F32; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "i64")) { + *out_type = WABT_TYPE_I64; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "f64")) { + *out_type = WABT_TYPE_F64; + return WABT_OK; + } else { + print_parse_error(ctx, "unknown type: \"" PRIstringslice "\"", + WABT_PRINTF_STRING_SLICE_ARG(type_str)); + return WABT_ERROR; + } +} + +static WabtResult parse_type_vector(Context* ctx, WabtTypeVector* out_types) { + WABT_ZERO_MEMORY(*out_types); + EXPECT("["); + bool first = true; + while (!match(ctx, "]")) { + if (!first) + EXPECT(","); + WabtType type; + CHECK_RESULT(parse_type_object(ctx, &type)); + first = false; + wabt_append_type_value(out_types, &type); + } + return WABT_OK; +} + +static WabtResult parse_const(Context* ctx, + WabtInterpreterTypedValue* out_value) { + WabtStringSlice type_str; + WabtStringSlice 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 (wabt_string_slice_eq_cstr(&type_str, "i32")) { + uint32_t value; + CHECK_RESULT(wabt_parse_int32(value_start, value_end, &value, + WABT_PARSE_UNSIGNED_ONLY)); + out_value->type = WABT_TYPE_I32; + out_value->value.i32 = value; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "f32")) { + uint32_t value_bits; + CHECK_RESULT(wabt_parse_int32(value_start, value_end, &value_bits, + WABT_PARSE_UNSIGNED_ONLY)); + out_value->type = WABT_TYPE_F32; + out_value->value.f32_bits = value_bits; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "i64")) { + uint64_t value; + CHECK_RESULT(wabt_parse_int64(value_start, value_end, &value, + WABT_PARSE_UNSIGNED_ONLY)); + out_value->type = WABT_TYPE_I64; + out_value->value.i64 = value; + return WABT_OK; + } else if (wabt_string_slice_eq_cstr(&type_str, "f64")) { + uint64_t value_bits; + CHECK_RESULT(wabt_parse_int64(value_start, value_end, &value_bits, + WABT_PARSE_UNSIGNED_ONLY)); + out_value->type = WABT_TYPE_F64; + out_value->value.f64_bits = value_bits; + return WABT_OK; + } else { + print_parse_error(ctx, "unknown type: \"" PRIstringslice "\"", + WABT_PRINTF_STRING_SLICE_ARG(type_str)); + return WABT_ERROR; + } +} + +static WabtResult parse_const_vector( + Context* ctx, + WabtInterpreterTypedValueVector* out_values) { + WABT_ZERO_MEMORY(*out_values); + EXPECT("["); + bool first = true; + while (!match(ctx, "]")) { + if (!first) + EXPECT(","); + WabtInterpreterTypedValue value; + CHECK_RESULT(parse_const(ctx, &value)); + wabt_append_interpreter_typed_value_value(out_values, &value); + first = false; + } + return WABT_OK; +} + +static WabtResult parse_action(Context* ctx, Action* out_action) { + WABT_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 WABT_OK; +} + +static char* create_module_path(Context* ctx, WabtStringSlice filename) { + const char* spec_json_filename = ctx->loc.filename; + WabtStringSlice dirname = get_dirname(spec_json_filename); + size_t path_len = dirname.length + 1 + filename.length + 1; + char* path = (char*)wabt_alloc(path_len); + + if (dirname.length == 0) { + wabt_snprintf(path, path_len, PRIstringslice, + WABT_PRINTF_STRING_SLICE_ARG(filename)); + } else { + wabt_snprintf(path, path_len, PRIstringslice "/" PRIstringslice, + WABT_PRINTF_STRING_SLICE_ARG(dirname), + WABT_PRINTF_STRING_SLICE_ARG(filename)); + } + + return path; +} + +static WabtResult on_module_command(Context* ctx, + WabtStringSlice filename, + WabtStringSlice name) { + char* path = create_module_path(ctx, filename); + WabtInterpreterEnvironmentMark mark = + wabt_mark_interpreter_environment(&ctx->env); + WabtResult result = + read_module(path, &ctx->env, &s_error_handler, &ctx->last_module); + + if (WABT_FAILED(result)) { + wabt_reset_interpreter_environment_to_mark(&ctx->env, mark); + print_command_error(ctx, "error reading module: \"%s\"", path); + wabt_free(path); + return WABT_ERROR; + } + + wabt_free(path); + + WabtInterpreterResult iresult = + run_start_function(&ctx->thread, ctx->last_module); + if (iresult != WABT_INTERPRETER_OK) { + wabt_reset_interpreter_environment_to_mark(&ctx->env, mark); + print_interpreter_result("error running start function", iresult); + return WABT_ERROR; + } + + if (!wabt_string_slice_is_empty(&name)) { + ctx->last_module->name = wabt_dup_string_slice(name); + + /* The binding also needs its own copy of the name. */ + WabtStringSlice binding_name = wabt_dup_string_slice(name); + WabtBinding* binding = + wabt_insert_binding(&ctx->env.module_bindings, &binding_name); + binding->index = ctx->env.modules.size - 1; + } + return WABT_OK; +} + +static WabtResult run_action(Context* ctx, + Action* action, + WabtInterpreterResult* out_iresult, + WabtInterpreterTypedValueVector* out_results, + RunVerbosity verbose) { + WABT_ZERO_MEMORY(*out_results); + + int module_index; + if (!wabt_string_slice_is_empty(&action->module_name)) { + module_index = wabt_find_binding_index_by_name(&ctx->env.module_bindings, + &action->module_name); + } else { + module_index = (int)ctx->env.modules.size - 1; + } + + assert(module_index < (int)ctx->env.modules.size); + WabtInterpreterModule* module = &ctx->env.modules.data[module_index]; + + switch (action->type) { + case ACTION_TYPE_INVOKE: + *out_iresult = + run_export_by_name(&ctx->thread, module, &action->field_name, + &action->args, out_results, verbose); + if (verbose) { + print_call(wabt_empty_string_slice(), action->field_name, &action->args, + out_results, *out_iresult); + } + return WABT_OK; + + case ACTION_TYPE_GET: { + *out_iresult = get_global_export_by_name( + &ctx->thread, module, &action->field_name, out_results); + return WABT_OK; + } + + default: + print_command_error(ctx, "invalid action type %d", action->type); + return WABT_ERROR; + } +} + +static WabtResult on_action_command(Context* ctx, Action* action) { + WabtInterpreterTypedValueVector results; + WabtInterpreterResult iresult; + + ctx->total++; + WabtResult result = run_action(ctx, action, &iresult, &results, RUN_VERBOSE); + if (WABT_SUCCEEDED(result)) { + if (iresult == WABT_INTERPRETER_OK) { + ctx->passed++; + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WABT_ERROR; + } + } + + wabt_destroy_interpreter_typed_value_vector(&results); + return result; +} + +static WabtBinaryErrorHandler* new_custom_error_handler(Context* ctx, + const char* desc) { + size_t header_size = ctx->source_filename.length + strlen(desc) + 100; + char* header = (char*)wabt_alloc(header_size); + wabt_snprintf(header, header_size, PRIstringslice ":%d: %s passed", + WABT_PRINTF_STRING_SLICE_ARG(ctx->source_filename), + ctx->command_line_number, desc); + + WabtDefaultErrorHandlerInfo* info = + (WabtDefaultErrorHandlerInfo*)wabt_alloc_zero( + sizeof(WabtDefaultErrorHandlerInfo)); + info->header = header; + info->out_file = stdout; + info->print_header = WABT_PRINT_ERROR_HEADER_ONCE; + + WabtBinaryErrorHandler* error_handler = + (WabtBinaryErrorHandler*)wabt_alloc_zero(sizeof(WabtBinaryErrorHandler)); + error_handler->on_error = wabt_default_binary_error_callback; + error_handler->user_data = info; + return error_handler; +} + +static void destroy_custom_error_handler( + WabtBinaryErrorHandler* error_handler) { + WabtDefaultErrorHandlerInfo* info = + (WabtDefaultErrorHandlerInfo*)error_handler->user_data; + wabt_free((void*)info->header); + wabt_free(info); + wabt_free(error_handler); +} + +static WabtResult on_assert_malformed_command(Context* ctx, + WabtStringSlice filename, + WabtStringSlice text) { + WabtBinaryErrorHandler* error_handler = + new_custom_error_handler(ctx, "assert_malformed"); + WabtInterpreterEnvironment env; + WABT_ZERO_MEMORY(env); + init_environment(&env); + + ctx->total++; + char* path = create_module_path(ctx, filename); + WabtInterpreterModule* module; + WabtResult result = read_module(path, &env, error_handler, &module); + if (WABT_FAILED(result)) { + ctx->passed++; + result = WABT_OK; + } else { + print_command_error(ctx, "expected module to be malformed: \"%s\"", path); + result = WABT_ERROR; + } + + wabt_free(path); + wabt_destroy_interpreter_environment(&env); + destroy_custom_error_handler(error_handler); + return result; +} + +static WabtResult on_register_command(Context* ctx, + WabtStringSlice name, + WabtStringSlice as) { + int module_index; + if (!wabt_string_slice_is_empty(&name)) { + /* The module names can be different than their registered names. We don't + * keep a hash for the module names (just the registered names), so we'll + * just iterate over all the modules to find it. */ + size_t i; + module_index = -1; + for (i = 0; i < ctx->env.modules.size; ++i) { + const WabtStringSlice* module_name = &ctx->env.modules.data[i].name; + if (!wabt_string_slice_is_empty(module_name) && + wabt_string_slices_are_equal(&name, module_name)) { + module_index = (int)i; + break; + } + } + } else { + module_index = (int)ctx->env.modules.size - 1; + } + + if (module_index < 0 || module_index >= (int)ctx->env.modules.size) { + print_command_error(ctx, "unknown module in register"); + return WABT_ERROR; + } + + WabtStringSlice dup_as = wabt_dup_string_slice(as); + WabtBinding* binding = + wabt_insert_binding(&ctx->env.registered_module_bindings, &dup_as); + binding->index = module_index; + return WABT_OK; +} + +static WabtResult on_assert_unlinkable_command(Context* ctx, + WabtStringSlice filename, + WabtStringSlice text) { + WabtBinaryErrorHandler* error_handler = + new_custom_error_handler(ctx, "assert_unlinkable"); + + ctx->total++; + char* path = create_module_path(ctx, filename); + WabtInterpreterModule* module; + WabtInterpreterEnvironmentMark mark = + wabt_mark_interpreter_environment(&ctx->env); + WabtResult result = read_module(path, &ctx->env, error_handler, &module); + wabt_reset_interpreter_environment_to_mark(&ctx->env, mark); + + if (WABT_FAILED(result)) { + ctx->passed++; + result = WABT_OK; + } else { + print_command_error(ctx, "expected module to be unlinkable: \"%s\"", path); + result = WABT_ERROR; + } + + wabt_free(path); + destroy_custom_error_handler(error_handler); + return result; +} + +static WabtResult on_assert_invalid_command(Context* ctx, + WabtStringSlice filename, + WabtStringSlice text) { + WabtBinaryErrorHandler* error_handler = + new_custom_error_handler(ctx, "assert_invalid"); + WabtInterpreterEnvironment env; + WABT_ZERO_MEMORY(env); + init_environment(&env); + + ctx->total++; + char* path = create_module_path(ctx, filename); + WabtInterpreterModule* module; + WabtResult result = read_module(path, &env, error_handler, &module); + if (WABT_FAILED(result)) { + ctx->passed++; + result = WABT_OK; + } else { + print_command_error(ctx, "expected module to be invalid: \"%s\"", path); + result = WABT_ERROR; + } + + wabt_free(path); + wabt_destroy_interpreter_environment(&env); + destroy_custom_error_handler(error_handler); + return result; +} + +static WabtResult on_assert_uninstantiable_command(Context* ctx, + WabtStringSlice filename, + WabtStringSlice text) { + ctx->total++; + char* path = create_module_path(ctx, filename); + WabtInterpreterModule* module; + WabtInterpreterEnvironmentMark mark = + wabt_mark_interpreter_environment(&ctx->env); + WabtResult result = read_module(path, &ctx->env, &s_error_handler, &module); + + if (WABT_SUCCEEDED(result)) { + WabtInterpreterResult iresult = run_start_function(&ctx->thread, module); + if (iresult == WABT_INTERPRETER_OK) { + print_command_error(ctx, "expected error running start function: \"%s\"", + path); + result = WABT_ERROR; + } else { + ctx->passed++; + result = WABT_OK; + } + } else { + print_command_error(ctx, "error reading module: \"%s\"", path); + result = WABT_ERROR; + } + + wabt_reset_interpreter_environment_to_mark(&ctx->env, mark); + wabt_free(path); + return result; +} + +static bool typed_values_are_equal(const WabtInterpreterTypedValue* tv1, + const WabtInterpreterTypedValue* tv2) { + if (tv1->type != tv2->type) + return false; + + switch (tv1->type) { + case WABT_TYPE_I32: return tv1->value.i32 == tv2->value.i32; + case WABT_TYPE_F32: return tv1->value.f32_bits == tv2->value.f32_bits; + case WABT_TYPE_I64: return tv1->value.i64 == tv2->value.i64; + case WABT_TYPE_F64: return tv1->value.f64_bits == tv2->value.f64_bits; + default: assert(0); return false; + } +} + +static WabtResult on_assert_return_command( + Context* ctx, + Action* action, + WabtInterpreterTypedValueVector* expected) { + WabtInterpreterTypedValueVector results; + WabtInterpreterResult iresult; + + ctx->total++; + WabtResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + + if (WABT_SUCCEEDED(result)) { + if (iresult == WABT_INTERPRETER_OK) { + if (results.size == expected->size) { + size_t i; + for (i = 0; i < results.size; ++i) { + const WabtInterpreterTypedValue* expected_tv = &expected->data[i]; + const WabtInterpreterTypedValue* 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 = WABT_ERROR; + } + } + } else { + print_command_error( + ctx, "result length mismatch in assert_return: expected %" PRIzd + ", got %" PRIzd, + expected->size, results.size); + result = WABT_ERROR; + } + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WABT_ERROR; + } + } + + if (WABT_SUCCEEDED(result)) + ctx->passed++; + + wabt_destroy_interpreter_typed_value_vector(&results); + return result; +} + +static WabtResult on_assert_return_nan_command(Context* ctx, Action* action) { + WabtInterpreterTypedValueVector results; + WabtInterpreterResult iresult; + + ctx->total++; + WabtResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + if (WABT_SUCCEEDED(result)) { + if (iresult == WABT_INTERPRETER_OK) { + if (results.size != 1) { + print_command_error(ctx, "expected one result, got %" PRIzd, + results.size); + result = WABT_ERROR; + } + + const WabtInterpreterTypedValue* actual = &results.data[0]; + switch (actual->type) { + case WABT_TYPE_F32: + if (!wabt_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 = WABT_ERROR; + } + break; + + case WABT_TYPE_F64: + if (!wabt_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 = WABT_ERROR; + } + break; + + default: + print_command_error(ctx, + "expected result type to be f32 or f64, got %s", + wabt_get_type_name(actual->type)); + result = WABT_ERROR; + break; + } + } else { + print_command_error(ctx, "unexpected trap: %s", s_trap_strings[iresult]); + result = WABT_ERROR; + } + } + + if (WABT_SUCCEEDED(result)) + ctx->passed++; + + wabt_destroy_interpreter_typed_value_vector(&results); + return WABT_OK; +} + +static WabtResult on_assert_trap_command(Context* ctx, + Action* action, + WabtStringSlice text) { + WabtInterpreterTypedValueVector results; + WabtInterpreterResult iresult; + + ctx->total++; + WabtResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + if (WABT_SUCCEEDED(result)) { + if (iresult != WABT_INTERPRETER_OK) { + ctx->passed++; + } else { + print_command_error(ctx, "expected trap: \"" PRIstringslice "\"", + WABT_PRINTF_STRING_SLICE_ARG(text)); + result = WABT_ERROR; + } + } + + wabt_destroy_interpreter_typed_value_vector(&results); + return result; +} + +static WabtResult on_assert_exhaustion_command(Context* ctx, + Action* action) { + WabtInterpreterTypedValueVector results; + WabtInterpreterResult iresult; + + ctx->total++; + WabtResult result = run_action(ctx, action, &iresult, &results, RUN_QUIET); + if (WABT_SUCCEEDED(result)) { + if (iresult == WABT_INTERPRETER_TRAP_CALL_STACK_EXHAUSTED || + iresult == WABT_INTERPRETER_TRAP_VALUE_STACK_EXHAUSTED) { + ctx->passed++; + } else { + print_command_error(ctx, "expected call stack exhaustion"); + result = WABT_ERROR; + } + } + + wabt_destroy_interpreter_typed_value_vector(&results); + return result; +} + +static void destroy_action(Action* action) { + wabt_destroy_interpreter_typed_value_vector(&action->args); +} + +static WabtResult parse_command(Context* ctx) { + EXPECT("{"); + EXPECT_KEY("type"); + if (match(ctx, "\"module\"")) { + WabtStringSlice name; + WabtStringSlice filename; + WABT_ZERO_MEMORY(name); + WABT_ZERO_MEMORY(filename); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_opt_name_string_value(ctx, &name)); + PARSE_KEY_STRING_VALUE("filename", &filename); + on_module_command(ctx, filename, name); + } else if (match(ctx, "\"action\"")) { + Action action; + WABT_ZERO_MEMORY(action); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + on_action_command(ctx, &action); + destroy_action(&action); + } else if (match(ctx, "\"register\"")) { + WabtStringSlice as; + WabtStringSlice name; + WABT_ZERO_MEMORY(as); + WABT_ZERO_MEMORY(name); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_opt_name_string_value(ctx, &name)); + PARSE_KEY_STRING_VALUE("as", &as); + on_register_command(ctx, name, as); + } else if (match(ctx, "\"assert_malformed\"")) { + WabtStringSlice filename; + WabtStringSlice text; + WABT_ZERO_MEMORY(filename); + WABT_ZERO_MEMORY(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\"")) { + WabtStringSlice filename; + WabtStringSlice text; + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + on_assert_invalid_command(ctx, filename, text); + } else if (match(ctx, "\"assert_unlinkable\"")) { + WabtStringSlice filename; + WabtStringSlice text; + WABT_ZERO_MEMORY(filename); + WABT_ZERO_MEMORY(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_uninstantiable\"")) { + WabtStringSlice filename; + WabtStringSlice text; + WABT_ZERO_MEMORY(filename); + WABT_ZERO_MEMORY(text); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + PARSE_KEY_STRING_VALUE("filename", &filename); + EXPECT(","); + PARSE_KEY_STRING_VALUE("text", &text); + on_assert_uninstantiable_command(ctx, filename, text); + } else if (match(ctx, "\"assert_return\"")) { + Action action; + WabtInterpreterTypedValueVector expected; + WABT_ZERO_MEMORY(action); + WABT_ZERO_MEMORY(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); + wabt_destroy_interpreter_typed_value_vector(&expected); + destroy_action(&action); + } else if (match(ctx, "\"assert_return_nan\"")) { + Action action; + WabtTypeVector expected; + WABT_ZERO_MEMORY(action); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + EXPECT(","); + /* Not needed for wabt-interp, but useful for other parsers. */ + EXPECT_KEY("expected"); + CHECK_RESULT(parse_type_vector(ctx, &expected)); + on_assert_return_nan_command(ctx, &action); + wabt_destroy_type_vector(&expected); + destroy_action(&action); + } else if (match(ctx, "\"assert_trap\"")) { + Action action; + WabtStringSlice text; + WABT_ZERO_MEMORY(action); + WABT_ZERO_MEMORY(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(&action); + } else if (match(ctx, "\"assert_exhaustion\"")) { + Action action; + WabtStringSlice text; + WABT_ZERO_MEMORY(action); + WABT_ZERO_MEMORY(text); + + EXPECT(","); + CHECK_RESULT(parse_line(ctx)); + EXPECT(","); + CHECK_RESULT(parse_action(ctx, &action)); + on_assert_exhaustion_command(ctx, &action); + destroy_action(&action); + } else { + print_command_error(ctx, "unknown command type"); + return WABT_ERROR; + } + EXPECT("}"); + return WABT_OK; +} + +static WabtResult parse_commands(Context* ctx) { + EXPECT("{"); + PARSE_KEY_STRING_VALUE("source_filename", &ctx->source_filename); + EXPECT(","); + EXPECT_KEY("commands"); + EXPECT("["); + bool first = true; + while (!match(ctx, "]")) { + if (!first) + EXPECT(","); + CHECK_RESULT(parse_command(ctx)); + first = false; + } + EXPECT("}"); + return WABT_OK; +} + +static void destroy_context(Context* ctx) { + wabt_destroy_interpreter_thread(&ctx->thread); + wabt_destroy_interpreter_environment(&ctx->env); + wabt_free(ctx->json_data); +} + +static WabtResult read_and_run_spec_json(const char* spec_json_filename) { + Context ctx; + WABT_ZERO_MEMORY(ctx); + ctx.loc.filename = spec_json_filename; + ctx.loc.line = 1; + ctx.loc.first_column = 1; + init_environment(&ctx.env); + wabt_init_interpreter_thread(&ctx.env, &ctx.thread, &s_thread_options); + + void* data; + size_t size; + WabtResult result = wabt_read_file(spec_json_filename, &data, &size); + if (WABT_FAILED(result)) + return WABT_ERROR; + + ctx.json_data = (char*)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; +} + +int main(int argc, char** argv) { + wabt_init_stdio(); + parse_options(argc, argv); + + s_stdout_stream = wabt_init_stdout_stream(); + + if (s_spec) { + return read_and_run_spec_json(s_infile); + } else { + return read_and_run_module(s_infile); + } +} |