blob: c0c032870f709c723886bc8f1038c0772866bce6 [file] [log] [blame] [edit]
/*
* 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;
}