summaryrefslogtreecommitdiff
path: root/src/tools/wasm-interp.c
diff options
context:
space:
mode:
authorBen Smith <binji@chromium.org>2016-10-24 14:02:18 -0700
committerBen Smith <binji@chromium.org>2016-11-03 13:05:36 -0700
commit5a1be55f5a50c1df5cdce6412a589bafa5fab1df (patch)
treeec4f75ff60d7363331dc45e615eb07d199e0f2da /src/tools/wasm-interp.c
parentfe4f7d4f0d85826f8babbda76607654380cfc050 (diff)
downloadwabt-5a1be55f5a50c1df5cdce6412a589bafa5fab1df.tar.gz
wabt-5a1be55f5a50c1df5cdce6412a589bafa5fab1df.tar.bz2
wabt-5a1be55f5a50c1df5cdce6412a589bafa5fab1df.zip
Use a new format for the spec JSON writer/parser
The previous spec JSON format was defined around modules. This is because the previous spec tests would only run assertions on the most recently read module. In addition, the previous spec writer would write the assertions as new exported functions in the module, and run those. The primary reason for doing this was to allow for passing/returning i64 values, which was necessary to test in a JavaScript host. Now that the primary host for running the spec tests is wasm-interp, we no longer need do bundle assertions into the module. Also, some of the new spec tests allow running exported functions on a module that is not the most-recently-read module. The new spec test format is now defined around commands. The commands map directly to the spec format commands, e.g. `module`, `assert_invalid`, `assert_trap`, etc.
Diffstat (limited to 'src/tools/wasm-interp.c')
-rw-r--r--src/tools/wasm-interp.c1322
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;
}