summaryrefslogtreecommitdiff
path: root/src/exe/wasm-interp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exe/wasm-interp.c')
-rw-r--r--src/exe/wasm-interp.c872
1 files changed, 872 insertions, 0 deletions
diff --git a/src/exe/wasm-interp.c b/src/exe/wasm-interp.c
new file mode 100644
index 00000000..85abc7e4
--- /dev/null
+++ b/src/exe/wasm-interp.c
@@ -0,0 +1,872 @@
+/*
+ * 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 "wasm-allocator.h"
+#include "wasm-binary-reader.h"
+#include "wasm-binary-reader-interpreter.h"
+#include "wasm-interpreter.h"
+#include "wasm-option-parser.h"
+#include "wasm-stack-allocator.h"
+#include "wasm-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 WasmBool s_verbose;
+static const char* s_infile;
+static WasmReadBinaryOptions s_read_binary_options =
+ WASM_READ_BINARY_OPTIONS_DEFAULT;
+static WasmInterpreterThreadOptions s_thread_options =
+ WASM_INTERPRETER_THREAD_OPTIONS_DEFAULT;
+static WasmBool s_trace;
+static WasmBool s_spec;
+static WasmBool s_run_all_exports;
+static WasmBool s_use_libc_allocator;
+static WasmStream* s_stdout_stream;
+
+static WasmBinaryErrorHandler s_error_handler =
+ WASM_BINARY_ERROR_HANDLER_DEFAULT;
+
+static WasmFileWriter s_log_stream_writer;
+static WasmStream s_log_stream;
+
+#define NOPE WASM_OPTION_NO_ARGUMENT
+#define YEP WASM_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,
+ FLAG_USE_LIBC_ALLOCATOR,
+ 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 WasmOption 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"},
+ {FLAG_USE_LIBC_ALLOCATOR, 0, "use-libc-allocator", NULL, NOPE,
+ "use malloc, free, etc. instead of stack allocator"},
+};
+WASM_STATIC_ASSERT(NUM_FLAGS == WASM_ARRAY_SIZE(s_options));
+
+static void on_option(struct WasmOptionParser* parser,
+ struct WasmOption* option,
+ const char* argument) {
+ switch (option->id) {
+ case FLAG_VERBOSE:
+ s_verbose++;
+ wasm_init_file_writer_existing(&s_log_stream_writer, stdout);
+ wasm_init_stream(&s_log_stream, &s_log_stream_writer.base, NULL);
+ s_read_binary_options.log_stream = &s_log_stream;
+ break;
+
+ case FLAG_HELP:
+ wasm_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 = WASM_TRUE;
+ break;
+
+ case FLAG_SPEC:
+ s_spec = WASM_TRUE;
+ break;
+
+ case FLAG_RUN_ALL_EXPORTS:
+ s_run_all_exports = WASM_TRUE;
+ break;
+
+ case FLAG_USE_LIBC_ALLOCATOR:
+ s_use_libc_allocator = WASM_TRUE;
+ break;
+ }
+}
+
+static void on_argument(struct WasmOptionParser* parser, const char* argument) {
+ s_infile = argument;
+}
+
+static void on_option_error(struct WasmOptionParser* parser,
+ const char* message) {
+ WASM_FATAL("%s\n", message);
+}
+
+static void parse_options(int argc, char** argv) {
+ WasmOptionParser parser;
+ WASM_ZERO_MEMORY(parser);
+ parser.description = s_description;
+ parser.options = s_options;
+ parser.num_options = WASM_ARRAY_SIZE(s_options);
+ parser.on_option = on_option;
+ parser.on_argument = on_argument;
+ parser.on_error = on_option_error;
+ wasm_parse_options(&parser, argc, argv);
+
+ if (s_spec && s_run_all_exports)
+ WASM_FATAL("--spec and --run-all-exports are incompatible.\n");
+
+ if (!s_infile) {
+ wasm_print_help(&parser, PROGRAM_NAME);
+ WASM_FATAL("No filename given.\n");
+ }
+}
+
+static WasmStringSlice 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;
+
+ WasmStringSlice result;
+ result.start = s;
+ result.length = last_slash - s;
+ return result;
+}
+
+static void print_typed_value(WasmInterpreterTypedValue* tv) {
+ switch (tv->type) {
+ case WASM_TYPE_I32:
+ printf("i32:%u", tv->value.i32);
+ break;
+
+ case WASM_TYPE_I64:
+ printf("i64:%" PRIu64, tv->value.i64);
+ break;
+
+ case WASM_TYPE_F32: {
+ float value;
+ memcpy(&value, &tv->value.f32_bits, sizeof(float));
+ printf("f32:%g", value);
+ break;
+ }
+
+ case WASM_TYPE_F64: {
+ double value;
+ memcpy(&value, &tv->value.f64_bits, sizeof(double));
+ printf("f64:%g", value);
+ break;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+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 WasmInterpreterFuncSignature* sig,
+ const WasmStringSlice* module_name,
+ const WasmStringSlice* field_name,
+ uint32_t num_args,
+ WasmInterpreterTypedValue* args,
+ uint32_t num_results,
+ WasmInterpreterTypedValue* out_results,
+ void* user_data) {
+ memset(out_results, 0, sizeof(WasmInterpreterTypedValue) * num_results);
+ uint32_t i;
+ 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(*module_name),
+ WASM_PRINTF_STRING_SLICE_ARG(*field_name));
+ print_typed_values(args, num_args);
+ printf(") => (");
+ print_typed_values(out_results, num_results);
+ printf(")\n");
+ return WASM_OK;
+}
+
+static WasmInterpreterResult run_defined_function(WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ uint32_t offset) {
+ thread->pc = offset;
+ WasmInterpreterResult iresult = WASM_INTERPRETER_OK;
+ uint32_t quantum = s_trace ? 1 : INSTRUCTION_QUANTUM;
+ uint32_t* call_stack_return_top = thread->call_stack_top;
+ while (iresult == WASM_INTERPRETER_OK) {
+ if (s_trace)
+ wasm_trace_pc(module, thread, s_stdout_stream);
+ iresult =
+ wasm_run_interpreter(module, thread, quantum, call_stack_return_top);
+ }
+ if (iresult != WASM_INTERPRETER_RETURNED) {
+ if (s_trace)
+ printf("!!! trapped: %s\n", s_trap_strings[iresult]);
+ return iresult;
+ }
+ /* use OK instead of RETURNED for consistency */
+ return WASM_INTERPRETER_OK;
+}
+
+static WasmInterpreterResult run_function(WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ uint32_t func_index) {
+ assert(func_index < module->funcs.size);
+ WasmInterpreterFunc* func = &module->funcs.data[func_index];
+ if (func->is_host) {
+ WasmInterpreterImport* import = &module->imports.data[func->import_index];
+ return wasm_call_host(module, thread, import);
+ } else {
+ return run_defined_function(module, thread, func->offset);
+ }
+}
+
+static WasmResult run_start_function(WasmInterpreterModule* module,
+ WasmInterpreterThread* thread) {
+ WasmResult result = WASM_OK;
+ if (module->start_func_index != WASM_INVALID_FUNC_INDEX) {
+ if (s_trace)
+ printf(">>> running start function:\n");
+ WasmInterpreterResult iresult =
+ run_function(module, thread, module->start_func_index);
+ if (iresult != WASM_INTERPRETER_OK) {
+ /* trap */
+ fprintf(stderr, "error: %s\n", s_trap_strings[iresult]);
+ result = WASM_ERROR;
+ }
+ }
+ return result;
+}
+
+static WasmInterpreterFuncSignature* get_export_signature(
+ WasmInterpreterModule* module,
+ WasmInterpreterExport* export) {
+ uint32_t func_index = export->index;
+ uint32_t sig_index = module->funcs.data[func_index].sig_index;
+ assert(sig_index < module->sigs.size);
+ return &module->sigs.data[sig_index];
+}
+
+static WasmInterpreterResult run_export(
+ WasmAllocator* allocator,
+ WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ WasmInterpreterExport* export,
+ WasmInterpreterFuncSignature* sig,
+ WasmInterpreterTypedValueVector* out_results) {
+ assert(export->kind == WASM_EXTERNAL_KIND_FUNC);
+
+ /* 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(module, 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;
+ }
+ }
+ }
+ }
+
+ thread->value_stack_top = thread->value_stack.data;
+ thread->call_stack_top = thread->call_stack.data;
+
+ return result;
+}
+
+static WasmInterpreterResult run_export_wrapper(
+ WasmAllocator* allocator,
+ WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ WasmInterpreterExport* export,
+ WasmInterpreterTypedValueVector* out_results,
+ RunVerbosity verbose) {
+ if (s_trace) {
+ printf(">>> running export \"" PRIstringslice "\":\n",
+ WASM_PRINTF_STRING_SLICE_ARG(export->name));
+ }
+
+ WasmInterpreterFuncSignature* sig = get_export_signature(module, export);
+ WasmInterpreterResult result =
+ run_export(allocator, module, thread, export, sig, out_results);
+
+ if (verbose) {
+ 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;
+}
+
+static WasmResult run_export_by_name(
+ WasmAllocator* allocator,
+ WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ WasmStringSlice* name,
+ WasmInterpreterResult* out_iresult,
+ WasmInterpreterTypedValueVector* out_results,
+ RunVerbosity verbose) {
+ WasmInterpreterExport* export =
+ wasm_get_interpreter_export_by_name(module, name);
+ if (!export)
+ return WASM_ERROR;
+
+ *out_iresult = run_export_wrapper(allocator, module, thread, export,
+ out_results, verbose);
+ return WASM_OK;
+}
+
+static void run_all_exports(WasmAllocator* allocator,
+ WasmInterpreterModule* module,
+ WasmInterpreterThread* thread,
+ RunVerbosity verbose) {
+ WasmInterpreterTypedValueVector results;
+ 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, module, thread, export, &results, verbose);
+ }
+ wasm_destroy_interpreter_typed_value_vector(allocator, &results);
+}
+
+static WasmResult read_module(WasmAllocator* allocator,
+ const char* module_filename,
+ WasmInterpreterModule* out_module,
+ WasmInterpreterThread* out_thread) {
+ WasmResult result;
+ void* data;
+ size_t size;
+ WASM_ZERO_MEMORY(*out_module);
+ WASM_ZERO_MEMORY(*out_thread);
+ result = wasm_read_file(allocator, module_filename, &data, &size);
+ if (WASM_SUCCEEDED(result)) {
+ WasmAllocator* memory_allocator = &g_wasm_libc_allocator;
+ result = wasm_read_binary_interpreter(allocator, memory_allocator, data,
+ size, &s_read_binary_options,
+ &s_error_handler, out_module);
+
+ if (WASM_SUCCEEDED(result)) {
+ if (s_verbose) {
+ wasm_disassemble_module(out_module, s_stdout_stream, 0,
+ out_module->istream.size);
+ }
+
+ result = wasm_init_interpreter_thread(allocator, out_module, out_thread,
+ &s_thread_options);
+ out_thread->host_func.callback = default_host_callback;
+ out_thread->host_func.user_data = NULL;
+ }
+ wasm_free(allocator, data);
+ }
+ return result;
+}
+
+static void destroy_module_and_thread(WasmAllocator* allocator,
+ WasmInterpreterModule* module,
+ WasmInterpreterThread* thread) {
+ wasm_destroy_interpreter_thread(allocator, thread);
+ wasm_destroy_interpreter_module(allocator, module);
+}
+
+static WasmResult read_and_run_module(WasmAllocator* allocator,
+ const char* module_filename) {
+ WasmResult result;
+ WasmInterpreterModule module;
+ WasmInterpreterThread thread;
+ result = read_module(allocator, module_filename, &module, &thread);
+ if (WASM_SUCCEEDED(result))
+ result = run_start_function(&module, &thread);
+
+ if (WASM_SUCCEEDED(result) && s_run_all_exports)
+ run_all_exports(allocator, &module, &thread, RUN_VERBOSE);
+ destroy_module_and_thread(allocator, &module, &thread);
+ return result;
+}
+
+static WasmResult read_and_run_spec_json(WasmAllocator* allocator,
+ const char* spec_json_filename) {
+ WasmResult result = WASM_OK;
+ WasmInterpreterModule module;
+ WasmInterpreterThread thread;
+ WasmStringSlice command_file;
+ WasmStringSlice command_name;
+ WasmAllocatorMark module_mark;
+ WasmInterpreterTypedValueVector result_values;
+ uint32_t command_line_no;
+ WasmBool has_module = WASM_FALSE;
+ uint32_t passed = 0;
+ uint32_t failed = 0;
+
+ WASM_ZERO_MEMORY(module);
+ WASM_ZERO_MEMORY(thread);
+ WASM_ZERO_MEMORY(command_file);
+ WASM_ZERO_MEMORY(command_name);
+ WASM_ZERO_MEMORY(module_mark);
+ WASM_ZERO_MEMORY(result_values);
+
+ void* data;
+ size_t size;
+ result = wasm_read_file(allocator, spec_json_filename, &data, &size);
+ if (WASM_FAILED(result))
+ return WASM_ERROR;
+
+ /* 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;
+
+ case TOP_OBJECT:
+ EXPECT_STR("\"modules\"");
+ SKIP_WS();
+ EXPECT(':');
+ SKIP_WS();
+ EXPECT('[');
+ state = MODULES_ARRAY;
+ break;
+
+ case MODULES_ARRAY:
+ if (MATCHES(']')) {
+ /* should only match with an empty module list */
+ SKIP_WS();
+ EXPECT('}');
+ state = DONE;
+ } else {
+ EXPECT('{');
+ state = MODULE_OBJECT;
+ }
+ break;
+
+ case END_MODULES_ARRAY:
+ EXPECT(']');
+ SKIP_WS();
+ EXPECT('}');
+ state = DONE;
+ break;
+
+ 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;
+
+ case END_MODULE_OBJECT:
+ EXPECT('}');
+ MAYBE_CONTINUE(MODULES_ARRAY);
+ destroy_module_and_thread(allocator, &module, &thread);
+ wasm_reset_to_mark(allocator, module_mark);
+ has_module = WASM_FALSE;
+ break;
+
+ 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));
+ } else {
+ wasm_snprintf(path, path_len, PRIstringslice "/" PRIstringslice,
+ WASM_PRINTF_STRING_SLICE_ARG(dirname),
+ WASM_PRINTF_STRING_SLICE_ARG(module_filename));
+ }
+
+ module_mark = wasm_mark(allocator);
+ result = read_module(allocator, path, &module, &thread);
+ if (WASM_FAILED(result))
+ goto fail;
+
+ has_module = WASM_TRUE;
+
+ result = run_start_function(&module, &thread);
+ if (WASM_FAILED(result))
+ goto fail;
+
+ MAYBE_CONTINUE(MODULE_OBJECT);
+ break;
+ }
+
+ case COMMANDS_ARRAY:
+ if (MATCHES(']')) {
+ /* should only match with an empty command array */
+ state = END_MODULE_OBJECT;
+ } else {
+ EXPECT('{');
+ state = COMMAND_OBJECT;
+ }
+ break;
+
+ case END_COMMANDS_ARRAY:
+ EXPECT(']');
+ MAYBE_CONTINUE(MODULE_OBJECT);
+ break;
+
+ 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;
+
+#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, &module, &thread, &command_name,
+ &iresult, &result_values, verbose);
+ if (WASM_FAILED(result)) {
+ FAILED("unknown export");
+ failed++;
+ goto fail;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ MAYBE_CONTINUE(COMMAND_OBJECT);
+ break;
+ }
+
+ case COMMAND_NAME: {
+ READ_STRING(command_name);
+ MAYBE_CONTINUE(COMMAND_OBJECT);
+ break;
+ }
+
+ case COMMAND_FILE: {
+ READ_STRING(command_file);
+ MAYBE_CONTINUE(COMMAND_OBJECT);
+ break;
+ }
+
+ 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;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+ uint32_t total = passed + failed;
+ printf("%d/%d tests passed.\n", passed, total);
+ result = passed == total ? WASM_OK : WASM_ERROR;
+
+ goto done;
+
+fail:
+ fprintf(stderr, "error parsing spec json file\n");
+ fprintf(stderr, "got this far: %" PRIzd ":> %.*s...\n", p - start, 20, p);
+ result = WASM_ERROR;
+
+done:
+ if (has_module)
+ destroy_module_and_thread(allocator, &module, &thread);
+ wasm_destroy_interpreter_typed_value_vector(allocator, &result_values);
+ wasm_free(allocator, data);
+ return result;
+}
+
+int main(int argc, char** argv) {
+ WasmStackAllocator stack_allocator;
+ WasmAllocator* allocator;
+
+ wasm_init_stdio();
+ parse_options(argc, argv);
+
+ s_stdout_stream = wasm_init_stdout_stream();
+
+ if (s_use_libc_allocator) {
+ allocator = &g_wasm_libc_allocator;
+ } else {
+ wasm_init_stack_allocator(&stack_allocator, &g_wasm_libc_allocator);
+ allocator = &stack_allocator.allocator;
+ }
+ WasmResult result;
+ if (s_spec) {
+ result = read_and_run_spec_json(allocator, s_infile);
+ } else {
+ result = read_and_run_module(allocator, s_infile);
+ }
+
+ wasm_print_allocator_stats(allocator);
+ wasm_destroy_allocator(allocator);
+ return result;
+}