| /* |
| * 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 <algorithm> |
| #include <memory> |
| #include <vector> |
| |
| #include "binary-error-handler.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" |
| |
| using namespace wabt; |
| |
| #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 ReadBinaryOptions s_read_binary_options = |
| WABT_READ_BINARY_OPTIONS_DEFAULT; |
| static InterpreterThreadOptions s_thread_options = |
| WABT_INTERPRETER_THREAD_OPTIONS_DEFAULT; |
| static bool s_trace; |
| static bool s_spec; |
| static bool s_run_all_exports; |
| |
| static std::unique_ptr<FileStream> s_log_stream; |
| static std::unique_ptr<FileStream> s_stdout_stream; |
| |
| #define NOPE HasArgument::No |
| #define YEP HasArgument::Yes |
| |
| enum class RunVerbosity { |
| Quiet = 0, |
| Verbose = 1, |
| }; |
| |
| 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 Option s_options[] = { |
| {FLAG_VERBOSE, 'v', "verbose", nullptr, NOPE, |
| "use multiple times for more info"}, |
| {FLAG_HELP, 'h', "help", nullptr, 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", nullptr, NOPE, "trace execution"}, |
| {FLAG_SPEC, 0, "spec", nullptr, NOPE, |
| "run spec tests (input file should be .json)"}, |
| {FLAG_RUN_ALL_EXPORTS, 0, "run-all-exports", nullptr, 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 OptionParser* parser, |
| struct Option* option, |
| const char* argument) { |
| switch (option->id) { |
| case FLAG_VERBOSE: |
| s_verbose++; |
| s_log_stream = FileStream::CreateStdout(); |
| s_read_binary_options.log_stream = s_log_stream.get(); |
| break; |
| |
| case FLAG_HELP: |
| 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 OptionParser* parser, const char* argument) { |
| s_infile = argument; |
| } |
| |
| static void on_option_error(struct OptionParser* parser, const char* message) { |
| WABT_FATAL("%s\n", message); |
| } |
| |
| static void parse_options(int argc, char** argv) { |
| OptionParser 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; |
| 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) { |
| print_help(&parser, PROGRAM_NAME); |
| WABT_FATAL("No filename given.\n"); |
| } |
| } |
| |
| static StringSlice get_dirname(const char* s) { |
| /* strip everything after and including the last slash (or backslash), e.g.: |
| * |
| * s = "foo/bar/baz", => "foo/bar" |
| * s = "/usr/local/include/stdio.h", => "/usr/local/include" |
| * s = "foo.bar", => "" |
| * s = "some\windows\directory", => "some\windows" |
| */ |
| const char* last_slash = strrchr(s, '/'); |
| const char* last_backslash = strrchr(s, '\\'); |
| if (!last_slash) |
| last_slash = s; |
| if (!last_backslash) |
| last_backslash = s; |
| |
| StringSlice result; |
| result.start = s; |
| result.length = std::max(last_slash, last_backslash) - 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 InterpreterTypedValue* tv) { |
| switch (tv->type) { |
| case Type::I32: |
| snprintf(buffer, size, "i32:%u", tv->value.i32); |
| break; |
| |
| case Type::I64: |
| snprintf(buffer, size, "i64:%" PRIu64, tv->value.i64); |
| break; |
| |
| case Type::F32: { |
| float value; |
| memcpy(&value, &tv->value.f32_bits, sizeof(float)); |
| snprintf(buffer, size, "f32:%f", value); |
| break; |
| } |
| |
| case Type::F64: { |
| double value; |
| memcpy(&value, &tv->value.f64_bits, sizeof(double)); |
| snprintf(buffer, size, "f64:%f", value); |
| break; |
| } |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| static void print_typed_value(const InterpreterTypedValue* tv) { |
| char buffer[MAX_TYPED_VALUE_CHARS]; |
| sprint_typed_value(buffer, sizeof(buffer), tv); |
| printf("%s", buffer); |
| } |
| |
| static void print_typed_value_vector( |
| const std::vector<InterpreterTypedValue>& values) { |
| for (size_t i = 0; i < values.size(); ++i) { |
| print_typed_value(&values[i]); |
| if (i != values.size() - 1) |
| printf(", "); |
| } |
| } |
| |
| static void print_interpreter_result(const char* desc, |
| InterpreterResult iresult) { |
| printf("%s: %s\n", desc, s_trap_strings[static_cast<size_t>(iresult)]); |
| } |
| |
| static void print_call(StringSlice module_name, |
| StringSlice func_name, |
| const std::vector<InterpreterTypedValue>& args, |
| const std::vector<InterpreterTypedValue>& results, |
| InterpreterResult 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 == InterpreterResult::Ok) { |
| if (results.size() > 0) { |
| printf(" "); |
| print_typed_value_vector(results); |
| } |
| printf("\n"); |
| } else { |
| print_interpreter_result(" error", iresult); |
| } |
| } |
| |
| static InterpreterResult run_defined_function(InterpreterThread* thread, |
| uint32_t offset) { |
| thread->pc = offset; |
| InterpreterResult iresult = InterpreterResult::Ok; |
| uint32_t quantum = s_trace ? 1 : INSTRUCTION_QUANTUM; |
| uint32_t* call_stack_return_top = thread->call_stack_top; |
| while (iresult == InterpreterResult::Ok) { |
| if (s_trace) |
| trace_pc(thread, s_stdout_stream.get()); |
| iresult = run_interpreter(thread, quantum, call_stack_return_top); |
| } |
| if (iresult != InterpreterResult::Returned) |
| return iresult; |
| /* use OK instead of RETURNED for consistency */ |
| return InterpreterResult::Ok; |
| } |
| |
| static InterpreterResult push_args( |
| InterpreterThread* thread, |
| const InterpreterFuncSignature* sig, |
| const std::vector<InterpreterTypedValue>& args) { |
| if (sig->param_types.size() != args.size()) |
| return InterpreterResult::ArgumentTypeMismatch; |
| |
| for (size_t i = 0; i < sig->param_types.size(); ++i) { |
| if (sig->param_types[i] != args[i].type) |
| return InterpreterResult::ArgumentTypeMismatch; |
| |
| InterpreterResult iresult = push_thread_value(thread, args[i].value); |
| if (iresult != InterpreterResult::Ok) { |
| thread->value_stack_top = thread->value_stack.data(); |
| return iresult; |
| } |
| } |
| return InterpreterResult::Ok; |
| } |
| |
| static void copy_results(InterpreterThread* thread, |
| const InterpreterFuncSignature* sig, |
| std::vector<InterpreterTypedValue>* 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); |
| |
| out_results->clear(); |
| for (size_t i = 0; i < expected_results; ++i) |
| out_results->emplace_back(sig->result_types[i], thread->value_stack[i]); |
| } |
| |
| static InterpreterResult run_function( |
| InterpreterThread* thread, |
| uint32_t func_index, |
| const std::vector<InterpreterTypedValue>& args, |
| std::vector<InterpreterTypedValue>* out_results) { |
| assert(func_index < thread->env->funcs.size()); |
| InterpreterFunc* func = thread->env->funcs[func_index].get(); |
| uint32_t sig_index = func->sig_index; |
| assert(sig_index < thread->env->sigs.size()); |
| InterpreterFuncSignature* sig = &thread->env->sigs[sig_index]; |
| |
| InterpreterResult iresult = push_args(thread, sig, args); |
| if (iresult == InterpreterResult::Ok) { |
| iresult = func->is_host |
| ? call_host(thread, func->as_host()) |
| : run_defined_function(thread, func->as_defined()->offset); |
| if (iresult == InterpreterResult::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 InterpreterResult run_start_function(InterpreterThread* thread, |
| DefinedInterpreterModule* module) { |
| if (module->start_func_index == WABT_INVALID_INDEX) |
| return InterpreterResult::Ok; |
| |
| if (s_trace) |
| printf(">>> running start function:\n"); |
| std::vector<InterpreterTypedValue> args; |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult = |
| run_function(thread, module->start_func_index, args, &results); |
| assert(results.size() == 0); |
| return iresult; |
| } |
| |
| static InterpreterResult run_export( |
| InterpreterThread* thread, |
| const InterpreterExport* export_, |
| const std::vector<InterpreterTypedValue>& args, |
| std::vector<InterpreterTypedValue>* out_results) { |
| if (s_trace) { |
| printf(">>> running export \"" PRIstringslice "\":\n", |
| WABT_PRINTF_STRING_SLICE_ARG(export_->name)); |
| } |
| |
| assert(export_->kind == ExternalKind::Func); |
| return run_function(thread, export_->index, args, out_results); |
| } |
| |
| static InterpreterResult run_export_by_name( |
| InterpreterThread* thread, |
| InterpreterModule* module, |
| const StringSlice* name, |
| const std::vector<InterpreterTypedValue>& args, |
| std::vector<InterpreterTypedValue>* out_results, |
| RunVerbosity verbose) { |
| InterpreterExport* export_ = get_interpreter_export_by_name(module, name); |
| if (!export_) |
| return InterpreterResult::UnknownExport; |
| if (export_->kind != ExternalKind::Func) |
| return InterpreterResult::ExportKindMismatch; |
| return run_export(thread, export_, args, out_results); |
| } |
| |
| static InterpreterResult get_global_export_by_name( |
| InterpreterThread* thread, |
| InterpreterModule* module, |
| const StringSlice* name, |
| std::vector<InterpreterTypedValue>* out_results) { |
| InterpreterExport* export_ = get_interpreter_export_by_name(module, name); |
| if (!export_) |
| return InterpreterResult::UnknownExport; |
| if (export_->kind != ExternalKind::Global) |
| return InterpreterResult::ExportKindMismatch; |
| |
| InterpreterGlobal* global = &thread->env->globals[export_->index]; |
| out_results->clear(); |
| out_results->push_back(global->typed_value); |
| return InterpreterResult::Ok; |
| } |
| |
| static void run_all_exports(InterpreterModule* module, |
| InterpreterThread* thread, |
| RunVerbosity verbose) { |
| std::vector<InterpreterTypedValue> args; |
| std::vector<InterpreterTypedValue> results; |
| for (const InterpreterExport& export_: module->exports) { |
| InterpreterResult iresult = run_export(thread, &export_, args, &results); |
| if (verbose == RunVerbosity::Verbose) { |
| print_call(empty_string_slice(), export_.name, args, results, iresult); |
| } |
| } |
| } |
| |
| static Result read_module(const char* module_filename, |
| InterpreterEnvironment* env, |
| BinaryErrorHandler* error_handler, |
| DefinedInterpreterModule** out_module) { |
| Result result; |
| char* data; |
| size_t size; |
| |
| *out_module = nullptr; |
| |
| result = read_file(module_filename, &data, &size); |
| if (WABT_SUCCEEDED(result)) { |
| result = read_binary_interpreter(env, data, size, &s_read_binary_options, |
| error_handler, out_module); |
| |
| if (WABT_SUCCEEDED(result)) { |
| if (s_verbose) |
| disassemble_module(env, s_stdout_stream.get(), *out_module); |
| } |
| delete[] data; |
| } |
| return result; |
| } |
| |
| static Result default_host_callback(const HostInterpreterFunc* func, |
| const InterpreterFuncSignature* sig, |
| uint32_t num_args, |
| InterpreterTypedValue* args, |
| uint32_t num_results, |
| InterpreterTypedValue* out_results, |
| void* user_data) { |
| memset(out_results, 0, sizeof(InterpreterTypedValue) * num_results); |
| for (uint32_t i = 0; i < num_results; ++i) |
| out_results[i].type = sig->result_types[i]; |
| |
| std::vector<InterpreterTypedValue> vec_args(args, args + num_args); |
| std::vector<InterpreterTypedValue> vec_results(out_results, |
| out_results + num_results); |
| |
| printf("called host "); |
| print_call(func->module_name, func->field_name, vec_args, vec_results, |
| InterpreterResult::Ok); |
| return Result::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(PrintErrorCallback callback, const char* format, ...) { |
| WABT_SNPRINTF_ALLOCA(buffer, length, format); |
| callback.print_error(buffer, callback.user_data); |
| } |
| |
| static Result spectest_import_func(InterpreterImport* import, |
| InterpreterFunc* func, |
| InterpreterFuncSignature* sig, |
| PrintErrorCallback callback, |
| void* user_data) { |
| if (string_slice_eq_cstr(&import->field_name, "print")) { |
| func->as_host()->callback = default_host_callback; |
| return Result::Ok; |
| } else { |
| print_error(callback, "unknown host function import " PRIimport, |
| PRINTF_IMPORT_ARG(*import)); |
| return Result::Error; |
| } |
| } |
| |
| static Result spectest_import_table(InterpreterImport* import, |
| InterpreterTable* table, |
| PrintErrorCallback callback, |
| void* user_data) { |
| if (string_slice_eq_cstr(&import->field_name, "table")) { |
| table->limits.has_max = true; |
| table->limits.initial = 10; |
| table->limits.max = 20; |
| return Result::Ok; |
| } else { |
| print_error(callback, "unknown host table import " PRIimport, |
| PRINTF_IMPORT_ARG(*import)); |
| return Result::Error; |
| } |
| } |
| |
| static Result spectest_import_memory(InterpreterImport* import, |
| InterpreterMemory* memory, |
| PrintErrorCallback callback, |
| void* user_data) { |
| if (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->data.resize(memory->page_limits.initial * WABT_MAX_PAGES); |
| return Result::Ok; |
| } else { |
| print_error(callback, "unknown host memory import " PRIimport, |
| PRINTF_IMPORT_ARG(*import)); |
| return Result::Error; |
| } |
| } |
| |
| static Result spectest_import_global(InterpreterImport* import, |
| InterpreterGlobal* global, |
| PrintErrorCallback callback, |
| void* user_data) { |
| if (string_slice_eq_cstr(&import->field_name, "global")) { |
| switch (global->typed_value.type) { |
| case Type::I32: |
| global->typed_value.value.i32 = 666; |
| break; |
| |
| case Type::F32: { |
| float value = 666.6f; |
| memcpy(&global->typed_value.value.f32_bits, &value, sizeof(value)); |
| break; |
| } |
| |
| case Type::I64: |
| global->typed_value.value.i64 = 666; |
| break; |
| |
| case 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 Result::Error; |
| } |
| |
| return Result::Ok; |
| } else { |
| print_error(callback, "unknown host global import " PRIimport, |
| PRINTF_IMPORT_ARG(*import)); |
| return Result::Error; |
| } |
| } |
| |
| static void init_environment(InterpreterEnvironment* env) { |
| HostInterpreterModule* host_module = |
| append_host_module(env, string_slice_from_cstr("spectest")); |
| host_module->import_delegate.import_func = spectest_import_func; |
| host_module->import_delegate.import_table = spectest_import_table; |
| host_module->import_delegate.import_memory = spectest_import_memory; |
| host_module->import_delegate.import_global = spectest_import_global; |
| } |
| |
| static Result read_and_run_module(const char* module_filename) { |
| Result result; |
| InterpreterEnvironment env; |
| DefinedInterpreterModule* module = nullptr; |
| InterpreterThread thread; |
| BinaryErrorHandlerFile error_handler; |
| |
| init_environment(&env); |
| init_interpreter_thread(&env, &thread, &s_thread_options); |
| result = read_module(module_filename, &env, &error_handler, &module); |
| if (WABT_SUCCEEDED(result)) { |
| InterpreterResult iresult = run_start_function(&thread, module); |
| if (iresult == InterpreterResult::Ok) { |
| if (s_run_all_exports) |
| run_all_exports(module, &thread, RunVerbosity::Verbose); |
| } else { |
| print_interpreter_result("error running start function", iresult); |
| } |
| } |
| return result; |
| } |
| |
| /* An extremely simple JSON parser that only knows how to parse the expected |
| * format from wast2wasm. */ |
| struct Context { |
| Context() |
| : last_module(nullptr), |
| json_data(nullptr), |
| json_data_size(0), |
| json_offset(0), |
| has_prev_loc(0), |
| command_line_number(0), |
| passed(0), |
| total(0) { |
| WABT_ZERO_MEMORY(source_filename); |
| WABT_ZERO_MEMORY(loc); |
| WABT_ZERO_MEMORY(prev_loc); |
| } |
| |
| InterpreterEnvironment env; |
| InterpreterThread thread; |
| DefinedInterpreterModule* last_module; |
| |
| /* Parsing info */ |
| char* json_data; |
| size_t json_data_size; |
| StringSlice source_filename; |
| size_t json_offset; |
| Location loc; |
| Location prev_loc; |
| bool has_prev_loc; |
| uint32_t command_line_number; |
| |
| /* Test info */ |
| int passed; |
| int total; |
| }; |
| |
| enum class ActionType { |
| Invoke, |
| Get, |
| }; |
| |
| struct Action { |
| Action() { |
| WABT_ZERO_MEMORY(module_name); |
| WABT_ZERO_MEMORY(field_name); |
| } |
| |
| ActionType type = ActionType::Invoke; |
| StringSlice module_name; |
| StringSlice field_name; |
| std::vector<InterpreterTypedValue> args; |
| }; |
| |
| #define CHECK_RESULT(x) \ |
| do { \ |
| if (WABT_FAILED(x)) \ |
| return Result::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); |
| Location 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 Result expect(Context* ctx, const char* s) { |
| if (match(ctx, s)) { |
| return Result::Ok; |
| } else { |
| print_parse_error(ctx, "expected %s", s); |
| return Result::Error; |
| } |
| } |
| |
| static Result expect_key(Context* ctx, const char* key) { |
| size_t keylen = strlen(key); |
| size_t quoted_len = keylen + 2 + 1; |
| char* quoted = static_cast<char*>(alloca(quoted_len)); |
| snprintf(quoted, quoted_len, "\"%s\"", key); |
| EXPECT(quoted); |
| EXPECT(":"); |
| return Result::Ok; |
| } |
| |
| static Result 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 + static_cast<uint32_t>(c - '0'); |
| if (result < last_result) { |
| print_parse_error(ctx, "uint32 overflow"); |
| return Result::Error; |
| } |
| } else { |
| putback_char(ctx); |
| break; |
| } |
| } |
| *out_int = result; |
| return Result::Ok; |
| } |
| |
| static Result parse_string(Context* ctx, StringSlice* out_string) { |
| WABT_ZERO_MEMORY(*out_string); |
| |
| skip_whitespace(ctx); |
| if (read_char(ctx) != '"') { |
| print_parse_error(ctx, "expected string"); |
| return Result::Error; |
| } |
| /* Modify json_data in-place so we can use the StringSlice 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 Result::Error; |
| } |
| uint16_t code = 0; |
| for (int 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 Result::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 Result::Ok; |
| } |
| |
| static Result parse_key_string_value(Context* ctx, |
| const char* key, |
| StringSlice* out_string) { |
| WABT_ZERO_MEMORY(*out_string); |
| EXPECT_KEY(key); |
| return parse_string(ctx, out_string); |
| } |
| |
| static Result parse_opt_name_string_value(Context* ctx, |
| StringSlice* out_string) { |
| WABT_ZERO_MEMORY(*out_string); |
| if (match(ctx, "\"name\"")) { |
| EXPECT(":"); |
| CHECK_RESULT(parse_string(ctx, out_string)); |
| EXPECT(","); |
| } |
| return Result::Ok; |
| } |
| |
| static Result parse_line(Context* ctx) { |
| EXPECT_KEY("line"); |
| CHECK_RESULT(parse_uint32(ctx, &ctx->command_line_number)); |
| return Result::Ok; |
| } |
| |
| static Result parse_type_object(Context* ctx, Type* out_type) { |
| StringSlice type_str; |
| EXPECT("{"); |
| PARSE_KEY_STRING_VALUE("type", &type_str); |
| EXPECT("}"); |
| |
| if (string_slice_eq_cstr(&type_str, "i32")) { |
| *out_type = Type::I32; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "f32")) { |
| *out_type = Type::F32; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "i64")) { |
| *out_type = Type::I64; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "f64")) { |
| *out_type = Type::F64; |
| return Result::Ok; |
| } else { |
| print_parse_error(ctx, "unknown type: \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(type_str)); |
| return Result::Error; |
| } |
| } |
| |
| static Result parse_type_vector(Context* ctx, TypeVector* out_types) { |
| out_types->clear(); |
| EXPECT("["); |
| bool first = true; |
| while (!match(ctx, "]")) { |
| if (!first) |
| EXPECT(","); |
| Type type; |
| CHECK_RESULT(parse_type_object(ctx, &type)); |
| first = false; |
| out_types->push_back(type); |
| } |
| return Result::Ok; |
| } |
| |
| static Result parse_const(Context* ctx, InterpreterTypedValue* out_value) { |
| StringSlice type_str; |
| StringSlice 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_eq_cstr(&type_str, "i32")) { |
| uint32_t value; |
| CHECK_RESULT(parse_int32(value_start, value_end, &value, |
| ParseIntType::UnsignedOnly)); |
| out_value->type = Type::I32; |
| out_value->value.i32 = value; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "f32")) { |
| uint32_t value_bits; |
| CHECK_RESULT(parse_int32(value_start, value_end, &value_bits, |
| ParseIntType::UnsignedOnly)); |
| out_value->type = Type::F32; |
| out_value->value.f32_bits = value_bits; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "i64")) { |
| uint64_t value; |
| CHECK_RESULT(parse_int64(value_start, value_end, &value, |
| ParseIntType::UnsignedOnly)); |
| out_value->type = Type::I64; |
| out_value->value.i64 = value; |
| return Result::Ok; |
| } else if (string_slice_eq_cstr(&type_str, "f64")) { |
| uint64_t value_bits; |
| CHECK_RESULT(parse_int64(value_start, value_end, &value_bits, |
| ParseIntType::UnsignedOnly)); |
| out_value->type = Type::F64; |
| out_value->value.f64_bits = value_bits; |
| return Result::Ok; |
| } else { |
| print_parse_error(ctx, "unknown type: \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(type_str)); |
| return Result::Error; |
| } |
| } |
| |
| static Result parse_const_vector( |
| Context* ctx, |
| std::vector<InterpreterTypedValue>* out_values) { |
| out_values->clear(); |
| EXPECT("["); |
| bool first = true; |
| while (!match(ctx, "]")) { |
| if (!first) |
| EXPECT(","); |
| InterpreterTypedValue value; |
| CHECK_RESULT(parse_const(ctx, &value)); |
| out_values->push_back(value); |
| first = false; |
| } |
| return Result::Ok; |
| } |
| |
| static Result parse_action(Context* ctx, Action* out_action) { |
| EXPECT_KEY("action"); |
| EXPECT("{"); |
| EXPECT_KEY("type"); |
| if (match(ctx, "\"invoke\"")) { |
| out_action->type = ActionType::Invoke; |
| } else { |
| EXPECT("\"get\""); |
| out_action->type = ActionType::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 == ActionType::Invoke) { |
| EXPECT(","); |
| EXPECT_KEY("args"); |
| CHECK_RESULT(parse_const_vector(ctx, &out_action->args)); |
| } |
| EXPECT("}"); |
| return Result::Ok; |
| } |
| |
| static char* create_module_path(Context* ctx, StringSlice filename) { |
| const char* spec_json_filename = ctx->loc.filename; |
| StringSlice dirname = get_dirname(spec_json_filename); |
| size_t path_len = dirname.length + 1 + filename.length + 1; |
| char* path = new char[path_len]; |
| |
| if (dirname.length == 0) { |
| snprintf(path, path_len, PRIstringslice, |
| WABT_PRINTF_STRING_SLICE_ARG(filename)); |
| } else { |
| snprintf(path, path_len, PRIstringslice "/" PRIstringslice, |
| WABT_PRINTF_STRING_SLICE_ARG(dirname), |
| WABT_PRINTF_STRING_SLICE_ARG(filename)); |
| } |
| |
| return path; |
| } |
| |
| static Result on_module_command(Context* ctx, |
| StringSlice filename, |
| StringSlice name) { |
| char* path = create_module_path(ctx, filename); |
| InterpreterEnvironmentMark mark = mark_interpreter_environment(&ctx->env); |
| BinaryErrorHandlerFile error_handler; |
| Result result = |
| read_module(path, &ctx->env, &error_handler, &ctx->last_module); |
| |
| if (WABT_FAILED(result)) { |
| reset_interpreter_environment_to_mark(&ctx->env, mark); |
| print_command_error(ctx, "error reading module: \"%s\"", path); |
| delete[] path; |
| return Result::Error; |
| } |
| |
| delete[] path; |
| |
| InterpreterResult iresult = |
| run_start_function(&ctx->thread, ctx->last_module); |
| if (iresult != InterpreterResult::Ok) { |
| reset_interpreter_environment_to_mark(&ctx->env, mark); |
| print_interpreter_result("error running start function", iresult); |
| return Result::Error; |
| } |
| |
| if (!string_slice_is_empty(&name)) { |
| ctx->last_module->name = dup_string_slice(name); |
| ctx->env.module_bindings.emplace(string_slice_to_string(name), |
| Binding(ctx->env.modules.size() - 1)); |
| } |
| return Result::Ok; |
| } |
| |
| static Result run_action(Context* ctx, |
| Action* action, |
| InterpreterResult* out_iresult, |
| std::vector<InterpreterTypedValue>* out_results, |
| RunVerbosity verbose) { |
| out_results->clear(); |
| |
| int module_index; |
| if (!string_slice_is_empty(&action->module_name)) { |
| module_index = ctx->env.module_bindings.find_index(action->module_name); |
| } else { |
| module_index = static_cast<int>(ctx->env.modules.size()) - 1; |
| } |
| |
| assert(module_index < static_cast<int>(ctx->env.modules.size())); |
| InterpreterModule* module = ctx->env.modules[module_index].get(); |
| |
| switch (action->type) { |
| case ActionType::Invoke: |
| *out_iresult = |
| run_export_by_name(&ctx->thread, module, &action->field_name, |
| action->args, out_results, verbose); |
| if (verbose == RunVerbosity::Verbose) { |
| print_call(empty_string_slice(), action->field_name, action->args, |
| *out_results, *out_iresult); |
| } |
| return Result::Ok; |
| |
| case ActionType::Get: { |
| *out_iresult = get_global_export_by_name( |
| &ctx->thread, module, &action->field_name, out_results); |
| return Result::Ok; |
| } |
| |
| default: |
| print_command_error(ctx, "invalid action type %d", |
| static_cast<int>(action->type)); |
| return Result::Error; |
| } |
| } |
| |
| static Result on_action_command(Context* ctx, Action* action) { |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult; |
| |
| ctx->total++; |
| Result result = |
| run_action(ctx, action, &iresult, &results, RunVerbosity::Verbose); |
| if (WABT_SUCCEEDED(result)) { |
| if (iresult == InterpreterResult::Ok) { |
| ctx->passed++; |
| } else { |
| print_command_error(ctx, "unexpected trap: %s", |
| s_trap_strings[static_cast<size_t>(iresult)]); |
| result = Result::Error; |
| } |
| } |
| |
| return result; |
| } |
| |
| class BinaryErrorHandlerAssert : public BinaryErrorHandlerFile { |
| public: |
| BinaryErrorHandlerAssert(Context* ctx, const char* desc) |
| : BinaryErrorHandlerFile(stdout, Header(ctx, desc), PrintHeader::Once) {} |
| |
| private: |
| std::string Header(Context* ctx, const char* desc) { |
| return string_printf(PRIstringslice ":%d: %s passed", |
| WABT_PRINTF_STRING_SLICE_ARG(ctx->source_filename), |
| ctx->command_line_number, desc); |
| } |
| }; |
| |
| static Result on_assert_malformed_command(Context* ctx, |
| StringSlice filename, |
| StringSlice text) { |
| BinaryErrorHandlerAssert error_handler(ctx, "assert_malformed"); |
| InterpreterEnvironment env; |
| init_environment(&env); |
| |
| ctx->total++; |
| char* path = create_module_path(ctx, filename); |
| DefinedInterpreterModule* module; |
| Result result = read_module(path, &env, &error_handler, &module); |
| if (WABT_FAILED(result)) { |
| ctx->passed++; |
| result = Result::Ok; |
| } else { |
| print_command_error(ctx, "expected module to be malformed: \"%s\"", path); |
| result = Result::Error; |
| } |
| |
| delete[] path; |
| return result; |
| } |
| |
| static Result on_register_command(Context* ctx, |
| StringSlice name, |
| StringSlice as) { |
| int module_index; |
| if (!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. */ |
| module_index = -1; |
| for (size_t i = 0; i < ctx->env.modules.size(); ++i) { |
| const StringSlice* module_name = &ctx->env.modules[i]->name; |
| if (!string_slice_is_empty(module_name) && |
| string_slices_are_equal(&name, module_name)) { |
| module_index = static_cast<int>(i); |
| break; |
| } |
| } |
| } else { |
| module_index = static_cast<int>(ctx->env.modules.size()) - 1; |
| } |
| |
| if (module_index < 0 || |
| module_index >= static_cast<int>(ctx->env.modules.size())) { |
| print_command_error(ctx, "unknown module in register"); |
| return Result::Error; |
| } |
| |
| ctx->env.registered_module_bindings.emplace(string_slice_to_string(as), |
| Binding(module_index)); |
| return Result::Ok; |
| } |
| |
| static Result on_assert_unlinkable_command(Context* ctx, |
| StringSlice filename, |
| StringSlice text) { |
| BinaryErrorHandlerAssert error_handler(ctx, "assert_unlinkable"); |
| |
| ctx->total++; |
| char* path = create_module_path(ctx, filename); |
| DefinedInterpreterModule* module; |
| InterpreterEnvironmentMark mark = mark_interpreter_environment(&ctx->env); |
| Result result = read_module(path, &ctx->env, &error_handler, &module); |
| reset_interpreter_environment_to_mark(&ctx->env, mark); |
| |
| if (WABT_FAILED(result)) { |
| ctx->passed++; |
| result = Result::Ok; |
| } else { |
| print_command_error(ctx, "expected module to be unlinkable: \"%s\"", path); |
| result = Result::Error; |
| } |
| |
| delete[] path; |
| return result; |
| } |
| |
| static Result on_assert_invalid_command(Context* ctx, |
| StringSlice filename, |
| StringSlice text) { |
| BinaryErrorHandlerAssert error_handler(ctx, "assert_invalid"); |
| InterpreterEnvironment env; |
| init_environment(&env); |
| |
| ctx->total++; |
| char* path = create_module_path(ctx, filename); |
| DefinedInterpreterModule* module; |
| Result result = read_module(path, &env, &error_handler, &module); |
| if (WABT_FAILED(result)) { |
| ctx->passed++; |
| result = Result::Ok; |
| } else { |
| print_command_error(ctx, "expected module to be invalid: \"%s\"", path); |
| result = Result::Error; |
| } |
| |
| delete[] path; |
| return result; |
| } |
| |
| static Result on_assert_uninstantiable_command(Context* ctx, |
| StringSlice filename, |
| StringSlice text) { |
| BinaryErrorHandlerFile error_handler; |
| ctx->total++; |
| char* path = create_module_path(ctx, filename); |
| DefinedInterpreterModule* module; |
| InterpreterEnvironmentMark mark = mark_interpreter_environment(&ctx->env); |
| Result result = read_module(path, &ctx->env, &error_handler, &module); |
| |
| if (WABT_SUCCEEDED(result)) { |
| InterpreterResult iresult = run_start_function(&ctx->thread, module); |
| if (iresult == InterpreterResult::Ok) { |
| print_command_error(ctx, "expected error running start function: \"%s\"", |
| path); |
| result = Result::Error; |
| } else { |
| ctx->passed++; |
| result = Result::Ok; |
| } |
| } else { |
| print_command_error(ctx, "error reading module: \"%s\"", path); |
| result = Result::Error; |
| } |
| |
| reset_interpreter_environment_to_mark(&ctx->env, mark); |
| delete[] path; |
| return result; |
| } |
| |
| static bool typed_values_are_equal(const InterpreterTypedValue* tv1, |
| const InterpreterTypedValue* tv2) { |
| if (tv1->type != tv2->type) |
| return false; |
| |
| switch (tv1->type) { |
| case Type::I32: |
| return tv1->value.i32 == tv2->value.i32; |
| case Type::F32: |
| return tv1->value.f32_bits == tv2->value.f32_bits; |
| case Type::I64: |
| return tv1->value.i64 == tv2->value.i64; |
| case Type::F64: |
| return tv1->value.f64_bits == tv2->value.f64_bits; |
| default: |
| assert(0); |
| return false; |
| } |
| } |
| |
| static Result on_assert_return_command( |
| Context* ctx, |
| Action* action, |
| const std::vector<InterpreterTypedValue>& expected) { |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult; |
| |
| ctx->total++; |
| Result result = |
| run_action(ctx, action, &iresult, &results, RunVerbosity::Quiet); |
| |
| if (WABT_SUCCEEDED(result)) { |
| if (iresult == InterpreterResult::Ok) { |
| if (results.size() == expected.size()) { |
| for (size_t i = 0; i < results.size(); ++i) { |
| const InterpreterTypedValue* expected_tv = &expected[i]; |
| const InterpreterTypedValue* actual_tv = &results[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 = Result::Error; |
| } |
| } |
| } else { |
| print_command_error( |
| ctx, "result length mismatch in assert_return: expected %" PRIzd |
| ", got %" PRIzd, |
| expected.size(), results.size()); |
| result = Result::Error; |
| } |
| } else { |
| print_command_error(ctx, "unexpected trap: %s", |
| s_trap_strings[static_cast<size_t>(iresult)]); |
| result = Result::Error; |
| } |
| } |
| |
| if (WABT_SUCCEEDED(result)) |
| ctx->passed++; |
| |
| return result; |
| } |
| |
| static Result on_assert_return_nan_command(Context* ctx, |
| Action* action, |
| bool canonical) { |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult; |
| |
| ctx->total++; |
| Result result = |
| run_action(ctx, action, &iresult, &results, RunVerbosity::Quiet); |
| if (WABT_SUCCEEDED(result)) { |
| if (iresult == InterpreterResult::Ok) { |
| if (results.size() != 1) { |
| print_command_error(ctx, "expected one result, got %" PRIzd, |
| results.size()); |
| result = Result::Error; |
| } |
| |
| const InterpreterTypedValue& actual = results[0]; |
| switch (actual.type) { |
| case Type::F32: { |
| typedef bool (*IsNanFunc)(uint32_t); |
| IsNanFunc is_nan_func = |
| canonical ? is_canonical_nan_f32 : is_arithmetic_nan_f32; |
| if (!is_nan_func(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 = Result::Error; |
| } |
| break; |
| } |
| |
| case Type::F64: { |
| typedef bool (*IsNanFunc)(uint64_t); |
| IsNanFunc is_nan_func = |
| canonical ? is_canonical_nan_f64 : is_arithmetic_nan_f64; |
| if (!is_nan_func(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 = Result::Error; |
| } |
| break; |
| } |
| |
| default: |
| print_command_error(ctx, |
| "expected result type to be f32 or f64, got %s", |
| get_type_name(actual.type)); |
| result = Result::Error; |
| break; |
| } |
| } else { |
| print_command_error(ctx, "unexpected trap: %s", |
| s_trap_strings[static_cast<int>(iresult)]); |
| result = Result::Error; |
| } |
| } |
| |
| if (WABT_SUCCEEDED(result)) |
| ctx->passed++; |
| |
| return Result::Ok; |
| } |
| |
| static Result on_assert_trap_command(Context* ctx, |
| Action* action, |
| StringSlice text) { |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult; |
| |
| ctx->total++; |
| Result result = |
| run_action(ctx, action, &iresult, &results, RunVerbosity::Quiet); |
| if (WABT_SUCCEEDED(result)) { |
| if (iresult != InterpreterResult::Ok) { |
| ctx->passed++; |
| } else { |
| print_command_error(ctx, "expected trap: \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(text)); |
| result = Result::Error; |
| } |
| } |
| |
| return result; |
| } |
| |
| static Result on_assert_exhaustion_command(Context* ctx, Action* action) { |
| std::vector<InterpreterTypedValue> results; |
| InterpreterResult iresult; |
| |
| ctx->total++; |
| Result result = |
| run_action(ctx, action, &iresult, &results, RunVerbosity::Quiet); |
| if (WABT_SUCCEEDED(result)) { |
| if (iresult == InterpreterResult::TrapCallStackExhausted || |
| iresult == InterpreterResult::TrapValueStackExhausted) { |
| ctx->passed++; |
| } else { |
| print_command_error(ctx, "expected call stack exhaustion"); |
| result = Result::Error; |
| } |
| } |
| |
| return result; |
| } |
| |
| static Result parse_command(Context* ctx) { |
| EXPECT("{"); |
| EXPECT_KEY("type"); |
| if (match(ctx, "\"module\"")) { |
| StringSlice name; |
| StringSlice 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; |
| |
| EXPECT(","); |
| CHECK_RESULT(parse_line(ctx)); |
| EXPECT(","); |
| CHECK_RESULT(parse_action(ctx, &action)); |
| on_action_command(ctx, &action); |
| } else if (match(ctx, "\"register\"")) { |
| StringSlice as; |
| StringSlice 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\"")) { |
| StringSlice filename; |
| StringSlice 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\"")) { |
| StringSlice filename; |
| StringSlice 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\"")) { |
| StringSlice filename; |
| StringSlice 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\"")) { |
| StringSlice filename; |
| StringSlice 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; |
| std::vector<InterpreterTypedValue> 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); |
| } else if (match(ctx, "\"assert_return_canonical_nan\"")) { |
| Action action; |
| TypeVector expected; |
| |
| 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, true); |
| } else if (match(ctx, "\"assert_return_arithmetic_nan\"")) { |
| Action action; |
| TypeVector expected; |
| |
| 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, false); |
| } else if (match(ctx, "\"assert_trap\"")) { |
| Action action; |
| StringSlice text; |
| 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); |
| } else if (match(ctx, "\"assert_exhaustion\"")) { |
| Action action; |
| StringSlice text; |
| WABT_ZERO_MEMORY(text); |
| |
| EXPECT(","); |
| CHECK_RESULT(parse_line(ctx)); |
| EXPECT(","); |
| CHECK_RESULT(parse_action(ctx, &action)); |
| on_assert_exhaustion_command(ctx, &action); |
| } else { |
| print_command_error(ctx, "unknown command type"); |
| return Result::Error; |
| } |
| EXPECT("}"); |
| return Result::Ok; |
| } |
| |
| static Result 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 Result::Ok; |
| } |
| |
| static void destroy_context(Context* ctx) { |
| delete[] ctx->json_data; |
| } |
| |
| static Result read_and_run_spec_json(const char* spec_json_filename) { |
| Context ctx; |
| ctx.loc.filename = spec_json_filename; |
| ctx.loc.line = 1; |
| ctx.loc.first_column = 1; |
| init_environment(&ctx.env); |
| init_interpreter_thread(&ctx.env, &ctx.thread, &s_thread_options); |
| |
| char* data; |
| size_t size; |
| Result result = read_file(spec_json_filename, &data, &size); |
| if (WABT_FAILED(result)) |
| return Result::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; |
| } |
| |
| int main(int argc, char** argv) { |
| init_stdio(); |
| parse_options(argc, argv); |
| |
| s_stdout_stream = FileStream::CreateStdout(); |
| |
| Result result; |
| if (s_spec) { |
| result = read_and_run_spec_json(s_infile); |
| } else { |
| result = read_and_run_module(s_infile); |
| } |
| return result != Result::Ok; |
| } |