blob: d6f95d94a08c11cb9ff04a33015ef0deb2588816 [file] [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 <algorithm>
#include <cassert>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
#include "src/binary-reader.h"
#include "src/cast.h"
#include "src/common.h"
#include "src/error-formatter.h"
#include "src/feature.h"
#include "src/interp/binary-reader-interp.h"
#include "src/interp/interp.h"
#include "src/literal.h"
#include "src/option-parser.h"
#include "src/resolve-names.h"
#include "src/stream.h"
#include "src/validator.h"
#include "src/wast-lexer.h"
#include "src/wast-parser.h"
using namespace wabt;
using namespace wabt::interp;
static int s_verbose;
static const char* s_infile;
static Thread::Options s_thread_options;
static Stream* s_trace_stream;
static Features s_features;
static std::unique_ptr<FileStream> s_log_stream;
static std::unique_ptr<FileStream> s_stdout_stream;
enum class RunVerbosity {
Quiet = 0,
Verbose = 1,
};
static const char s_description[] =
R"( read a Spectest JSON file, and run its tests in the interpreter.
examples:
# parse test.json and run the spec tests
$ spectest-interp test.json
)";
static void ParseOptions(int argc, char** argv) {
OptionParser parser("spectest-interp", s_description);
parser.AddOption('v', "verbose", "Use multiple times for more info", []() {
s_verbose++;
s_log_stream = FileStream::CreateStdout();
});
s_features.AddOptions(&parser);
parser.AddOption('V', "value-stack-size", "SIZE",
"Size in elements of the value stack",
[](const std::string& argument) {
// TODO(binji): validate.
s_thread_options.value_stack_size = atoi(argument.c_str());
});
parser.AddOption('C', "call-stack-size", "SIZE",
"Size in elements of the call stack",
[](const std::string& argument) {
// TODO(binji): validate.
s_thread_options.call_stack_size = atoi(argument.c_str());
});
parser.AddOption('t', "trace", "Trace execution",
[]() { s_trace_stream = s_stdout_stream.get(); });
parser.AddArgument("filename", OptionParser::ArgumentCount::One,
[](const char* argument) { s_infile = argument; });
parser.Parse(argc, argv);
}
namespace spectest {
class Command;
typedef std::unique_ptr<Command> CommandPtr;
typedef std::vector<CommandPtr> CommandPtrVector;
class Script {
public:
std::string filename;
CommandPtrVector commands;
};
class Command {
public:
WABT_DISALLOW_COPY_AND_ASSIGN(Command);
Command() = delete;
virtual ~Command() = default;
CommandType type;
uint32_t line = 0;
protected:
explicit Command(CommandType type) : type(type) {}
};
template <CommandType TypeEnum>
class CommandMixin : public Command {
public:
static bool classof(const Command* cmd) { return cmd->type == TypeEnum; }
CommandMixin() : Command(TypeEnum) {}
};
enum class ModuleType {
Text,
Binary,
};
class ModuleCommand : public CommandMixin<CommandType::Module> {
public:
ModuleType module = ModuleType::Binary;
std::string filename;
std::string name;
};
class Action {
public:
ActionType type = ActionType::Invoke;
std::string module_name;
std::string field_name;
TypedValues args;
};
template <CommandType TypeEnum>
class ActionCommandBase : public CommandMixin<TypeEnum> {
public:
Action action;
};
typedef ActionCommandBase<CommandType::Action> ActionCommand;
typedef ActionCommandBase<CommandType::AssertReturnCanonicalNan>
AssertReturnCanonicalNanCommand;
typedef ActionCommandBase<CommandType::AssertReturnArithmeticNan>
AssertReturnArithmeticNanCommand;
class RegisterCommand : public CommandMixin<CommandType::Register> {
public:
std::string as;
std::string name;
};
class AssertReturnCommand : public CommandMixin<CommandType::AssertReturn> {
public:
Action action;
TypedValues expected;
};
class AssertReturnFuncCommand
: public CommandMixin<CommandType::AssertReturnFunc> {
public:
Action action;
};
template <CommandType TypeEnum>
class AssertTrapCommandBase : public CommandMixin<TypeEnum> {
public:
Action action;
std::string text;
};
typedef AssertTrapCommandBase<CommandType::AssertTrap> AssertTrapCommand;
typedef AssertTrapCommandBase<CommandType::AssertExhaustion>
AssertExhaustionCommand;
template <CommandType TypeEnum>
class AssertModuleCommand : public CommandMixin<TypeEnum> {
public:
ModuleType type = ModuleType::Binary;
std::string filename;
std::string text;
};
typedef AssertModuleCommand<CommandType::AssertMalformed>
AssertMalformedCommand;
typedef AssertModuleCommand<CommandType::AssertInvalid> AssertInvalidCommand;
typedef AssertModuleCommand<CommandType::AssertUnlinkable>
AssertUnlinkableCommand;
typedef AssertModuleCommand<CommandType::AssertUninstantiable>
AssertUninstantiableCommand;
// An extremely simple JSON parser that only knows how to parse the expected
// format from wat2wasm.
class JSONParser {
public:
JSONParser() {}
wabt::Result ReadFile(string_view spec_json_filename);
wabt::Result ParseScript(Script* out_script);
private:
void WABT_PRINTF_FORMAT(2, 3) PrintError(const char* format, ...);
void PutbackChar();
int ReadChar();
void SkipWhitespace();
bool Match(const char* s);
wabt::Result Expect(const char* s);
wabt::Result ExpectKey(const char* key);
wabt::Result ParseUint32(uint32_t* out_int);
wabt::Result ParseString(std::string* out_string);
wabt::Result ParseKeyStringValue(const char* key, std::string* out_string);
wabt::Result ParseOptNameStringValue(std::string* out_string);
wabt::Result ParseLine(uint32_t* out_line_number);
wabt::Result ParseTypeObject(Type* out_type);
wabt::Result ParseTypeVector(TypeVector* out_types);
wabt::Result ParseConst(TypedValue* out_value);
wabt::Result ParseConstVector(TypedValues* out_values);
wabt::Result ParseAction(Action* out_action);
wabt::Result ParseActionResult();
wabt::Result ParseModuleType(ModuleType* out_type);
std::string CreateModulePath(string_view filename);
wabt::Result ParseFilename(std::string* out_filename);
wabt::Result ParseCommand(CommandPtr* out_command);
// Parsing info.
std::vector<uint8_t> json_data_;
size_t json_offset_ = 0;
Location loc_;
Location prev_loc_;
bool has_prev_loc_ = false;
};
#define EXPECT(x) CHECK_RESULT(Expect(x))
#define EXPECT_KEY(x) CHECK_RESULT(ExpectKey(x))
#define PARSE_KEY_STRING_VALUE(key, value) \
CHECK_RESULT(ParseKeyStringValue(key, value))
wabt::Result JSONParser::ReadFile(string_view spec_json_filename) {
loc_.filename = spec_json_filename;
loc_.line = 1;
loc_.first_column = 1;
return wabt::ReadFile(spec_json_filename, &json_data_);
}
void JSONParser::PrintError(const char* format, ...) {
WABT_SNPRINTF_ALLOCA(buffer, length, format);
fprintf(stderr, "%s:%d:%d: %s\n", loc_.filename.to_string().c_str(),
loc_.line, loc_.first_column, buffer);
}
void JSONParser::PutbackChar() {
assert(has_prev_loc_);
json_offset_--;
loc_ = prev_loc_;
has_prev_loc_ = false;
}
int JSONParser::ReadChar() {
if (json_offset_ >= json_data_.size()) {
return -1;
}
prev_loc_ = loc_;
char c = json_data_[json_offset_++];
if (c == '\n') {
loc_.line++;
loc_.first_column = 1;
} else {
loc_.first_column++;
}
has_prev_loc_ = true;
return c;
}
void JSONParser::SkipWhitespace() {
while (1) {
switch (ReadChar()) {
case -1:
return;
case ' ':
case '\t':
case '\n':
case '\r':
break;
default:
PutbackChar();
return;
}
}
}
bool JSONParser::Match(const char* s) {
SkipWhitespace();
Location start_loc = loc_;
size_t start_offset = json_offset_;
while (*s && *s == ReadChar())
s++;
if (*s == 0) {
return true;
} else {
json_offset_ = start_offset;
loc_ = start_loc;
return false;
}
}
wabt::Result JSONParser::Expect(const char* s) {
if (Match(s)) {
return wabt::Result::Ok;
} else {
PrintError("expected %s", s);
return wabt::Result::Error;
}
}
wabt::Result JSONParser::ExpectKey(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 wabt::Result::Ok;
}
wabt::Result JSONParser::ParseUint32(uint32_t* out_int) {
uint32_t result = 0;
SkipWhitespace();
while (1) {
int c = ReadChar();
if (c >= '0' && c <= '9') {
uint32_t last_result = result;
result = result * 10 + static_cast<uint32_t>(c - '0');
if (result < last_result) {
PrintError("uint32 overflow");
return wabt::Result::Error;
}
} else {
PutbackChar();
break;
}
}
*out_int = result;
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseString(std::string* out_string) {
out_string->clear();
SkipWhitespace();
if (ReadChar() != '"') {
PrintError("expected string");
return wabt::Result::Error;
}
while (1) {
int c = ReadChar();
if (c == '"') {
break;
} else if (c == '\\') {
/* The only escape supported is \uxxxx. */
c = ReadChar();
if (c != 'u') {
PrintError("expected escape: \\uxxxx");
return wabt::Result::Error;
}
uint16_t code = 0;
for (int i = 0; i < 4; ++i) {
c = ReadChar();
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 {
PrintError("expected hex char");
return wabt::Result::Error;
}
code = (code << 4) + cval;
}
if (code < 256) {
*out_string += code;
} else {
PrintError("only escape codes < 256 allowed, got %u\n", code);
}
} else {
*out_string += c;
}
}
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseKeyStringValue(const char* key,
std::string* out_string) {
out_string->clear();
EXPECT_KEY(key);
return ParseString(out_string);
}
wabt::Result JSONParser::ParseOptNameStringValue(std::string* out_string) {
out_string->clear();
if (Match("\"name\"")) {
EXPECT(":");
CHECK_RESULT(ParseString(out_string));
EXPECT(",");
}
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseLine(uint32_t* out_line_number) {
EXPECT_KEY("line");
CHECK_RESULT(ParseUint32(out_line_number));
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseTypeObject(Type* out_type) {
std::string type_str;
EXPECT("{");
PARSE_KEY_STRING_VALUE("type", &type_str);
EXPECT("}");
if (type_str == "i32") {
*out_type = Type::I32;
return wabt::Result::Ok;
} else if (type_str == "f32") {
*out_type = Type::F32;
return wabt::Result::Ok;
} else if (type_str == "i64") {
*out_type = Type::I64;
return wabt::Result::Ok;
} else if (type_str == "f64") {
*out_type = Type::F64;
return wabt::Result::Ok;
} else if (type_str == "v128") {
*out_type = Type::V128;
return wabt::Result::Ok;
} else if (type_str == "funcref") {
*out_type = Type::Funcref;
return wabt::Result::Ok;
} else if (type_str == "anyref") {
*out_type = Type::Anyref;
return wabt::Result::Ok;
} else {
PrintError("unknown type: \"%s\"", type_str.c_str());
return wabt::Result::Error;
}
}
wabt::Result JSONParser::ParseTypeVector(TypeVector* out_types) {
out_types->clear();
EXPECT("[");
bool first = true;
while (!Match("]")) {
if (!first) {
EXPECT(",");
}
Type type;
CHECK_RESULT(ParseTypeObject(&type));
first = false;
out_types->push_back(type);
}
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseConst(TypedValue* out_value) {
std::string type_str;
std::string 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.data();
const char* value_end = value_str.data() + value_str.size();
if (type_str == "i32") {
uint32_t value;
CHECK_RESULT(
ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly));
out_value->type = Type::I32;
out_value->value.i32 = value;
return wabt::Result::Ok;
} else if (type_str == "f32") {
uint32_t value_bits;
CHECK_RESULT(ParseInt32(value_start, value_end, &value_bits,
ParseIntType::UnsignedOnly));
out_value->type = Type::F32;
out_value->value.f32_bits = value_bits;
return wabt::Result::Ok;
} else if (type_str == "i64") {
uint64_t value;
CHECK_RESULT(
ParseInt64(value_start, value_end, &value, ParseIntType::UnsignedOnly));
out_value->type = Type::I64;
out_value->value.i64 = value;
return wabt::Result::Ok;
} else if (type_str == "f64") {
uint64_t value_bits;
CHECK_RESULT(ParseInt64(value_start, value_end, &value_bits,
ParseIntType::UnsignedOnly));
out_value->type = Type::F64;
out_value->value.f64_bits = value_bits;
return wabt::Result::Ok;
} else if (type_str == "v128") {
v128 value_bits;
CHECK_RESULT(ParseUint128(value_start, value_end, &value_bits));
out_value->type = Type::V128;
out_value->value.vec128 = value_bits;
return wabt::Result::Ok;
} else if (type_str == "nullref") {
out_value->type = Type::Nullref;
out_value->value.ref = {RefType::Null, 0};
return wabt::Result::Ok;
} else if (type_str == "hostref") {
uint32_t value;
CHECK_RESULT(
ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly));
out_value->type = Type::Hostref;
out_value->value.ref = {RefType::Host, value};
return wabt::Result::Ok;
} else if (type_str == "funcref") {
uint32_t value;
CHECK_RESULT(
ParseInt32(value_start, value_end, &value, ParseIntType::UnsignedOnly));
out_value->type = Type::Funcref;
out_value->value.ref = {RefType::Func, value};
return wabt::Result::Ok;
} else {
PrintError("unknown concrete type: \"%s\"", type_str.c_str());
return wabt::Result::Error;
}
}
wabt::Result JSONParser::ParseConstVector(TypedValues* out_values) {
out_values->clear();
EXPECT("[");
bool first = true;
while (!Match("]")) {
if (!first) {
EXPECT(",");
}
TypedValue value;
CHECK_RESULT(ParseConst(&value));
out_values->push_back(value);
first = false;
}
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseAction(Action* out_action) {
EXPECT_KEY("action");
EXPECT("{");
EXPECT_KEY("type");
if (Match("\"invoke\"")) {
out_action->type = ActionType::Invoke;
} else {
EXPECT("\"get\"");
out_action->type = ActionType::Get;
}
EXPECT(",");
if (Match("\"module\"")) {
EXPECT(":");
CHECK_RESULT(ParseString(&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(ParseConstVector(&out_action->args));
}
EXPECT("}");
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseActionResult() {
// Not needed for wabt-interp, but useful for other parsers.
EXPECT_KEY("expected");
TypeVector expected;
CHECK_RESULT(ParseTypeVector(&expected));
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseModuleType(ModuleType* out_type) {
std::string module_type_str;
PARSE_KEY_STRING_VALUE("module_type", &module_type_str);
if (module_type_str == "text") {
*out_type = ModuleType::Text;
return wabt::Result::Ok;
} else if (module_type_str == "binary") {
*out_type = ModuleType::Binary;
return wabt::Result::Ok;
} else {
PrintError("unknown module type: \"%s\"", module_type_str.c_str());
return wabt::Result::Error;
}
}
static string_view GetDirname(string_view path) {
// 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"
size_t last_slash = path.find_last_of('/');
size_t last_backslash = path.find_last_of('\\');
if (last_slash == string_view::npos) {
last_slash = 0;
}
if (last_backslash == string_view::npos) {
last_backslash = 0;
}
return path.substr(0, std::max(last_slash, last_backslash));
}
std::string JSONParser::CreateModulePath(string_view filename) {
string_view spec_json_filename = loc_.filename;
string_view dirname = GetDirname(spec_json_filename);
std::string path;
if (dirname.size() == 0) {
path = filename.to_string();
} else {
path = dirname.to_string();
path += '/';
path += filename.to_string();
}
ConvertBackslashToSlash(&path);
return path;
}
wabt::Result JSONParser::ParseFilename(std::string* out_filename) {
PARSE_KEY_STRING_VALUE("filename", out_filename);
*out_filename = CreateModulePath(*out_filename);
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseCommand(CommandPtr* out_command) {
EXPECT("{");
EXPECT_KEY("type");
if (Match("\"module\"")) {
auto command = MakeUnique<ModuleCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseOptNameStringValue(&command->name));
CHECK_RESULT(ParseFilename(&command->filename));
*out_command = std::move(command);
} else if (Match("\"action\"")) {
auto command = MakeUnique<ActionCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
CHECK_RESULT(ParseActionResult());
*out_command = std::move(command);
} else if (Match("\"register\"")) {
auto command = MakeUnique<RegisterCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseOptNameStringValue(&command->name));
PARSE_KEY_STRING_VALUE("as", &command->as);
*out_command = std::move(command);
} else if (Match("\"assert_malformed\"")) {
auto command = MakeUnique<AssertMalformedCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseFilename(&command->filename));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseModuleType(&command->type));
*out_command = std::move(command);
} else if (Match("\"assert_invalid\"")) {
auto command = MakeUnique<AssertInvalidCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseFilename(&command->filename));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseModuleType(&command->type));
*out_command = std::move(command);
} else if (Match("\"assert_unlinkable\"")) {
auto command = MakeUnique<AssertUnlinkableCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseFilename(&command->filename));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseModuleType(&command->type));
*out_command = std::move(command);
} else if (Match("\"assert_uninstantiable\"")) {
auto command = MakeUnique<AssertUninstantiableCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseFilename(&command->filename));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseModuleType(&command->type));
*out_command = std::move(command);
} else if (Match("\"assert_return\"")) {
auto command = MakeUnique<AssertReturnCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
EXPECT_KEY("expected");
CHECK_RESULT(ParseConstVector(&command->expected));
*out_command = std::move(command);
} else if (Match("\"assert_return_func\"")) {
auto command = MakeUnique<AssertReturnFuncCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
*out_command = std::move(command);
} else if (Match("\"assert_return_canonical_nan\"")) {
auto command = MakeUnique<AssertReturnCanonicalNanCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
CHECK_RESULT(ParseActionResult());
*out_command = std::move(command);
} else if (Match("\"assert_return_arithmetic_nan\"")) {
auto command = MakeUnique<AssertReturnArithmeticNanCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
CHECK_RESULT(ParseActionResult());
*out_command = std::move(command);
} else if (Match("\"assert_trap\"")) {
auto command = MakeUnique<AssertTrapCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseActionResult());
*out_command = std::move(command);
} else if (Match("\"assert_exhaustion\"")) {
auto command = MakeUnique<AssertExhaustionCommand>();
EXPECT(",");
CHECK_RESULT(ParseLine(&command->line));
EXPECT(",");
CHECK_RESULT(ParseAction(&command->action));
EXPECT(",");
PARSE_KEY_STRING_VALUE("text", &command->text);
EXPECT(",");
CHECK_RESULT(ParseActionResult());
*out_command = std::move(command);
} else {
PrintError("unknown command type");
return wabt::Result::Error;
}
EXPECT("}");
return wabt::Result::Ok;
}
wabt::Result JSONParser::ParseScript(Script* out_script) {
EXPECT("{");
PARSE_KEY_STRING_VALUE("source_filename", &out_script->filename);
EXPECT(",");
EXPECT_KEY("commands");
EXPECT("[");
bool first = true;
while (!Match("]")) {
CommandPtr command;
if (!first) {
EXPECT(",");
}
CHECK_RESULT(ParseCommand(&command));
out_script->commands.push_back(std::move(command));
first = false;
}
EXPECT("}");
return wabt::Result::Ok;
}
class CommandRunner {
public:
CommandRunner();
wabt::Result Run(const Script& script);
int passed() const { return passed_; }
int total() const { return total_; }
private:
void WABT_PRINTF_FORMAT(3, 4)
PrintError(uint32_t line_number, const char* format, ...);
ExecResult RunAction(int line_number,
const Action* action,
RunVerbosity verbose);
wabt::Result OnModuleCommand(const ModuleCommand*);
wabt::Result OnActionCommand(const ActionCommand*);
wabt::Result OnRegisterCommand(const RegisterCommand*);
wabt::Result OnAssertMalformedCommand(const AssertMalformedCommand*);
wabt::Result OnAssertUnlinkableCommand(const AssertUnlinkableCommand*);
wabt::Result OnAssertInvalidCommand(const AssertInvalidCommand*);
wabt::Result OnAssertUninstantiableCommand(
const AssertUninstantiableCommand*);
wabt::Result OnAssertReturnCommand(const AssertReturnCommand*);
wabt::Result OnAssertReturnFuncCommand(const AssertReturnFuncCommand*);
template <typename NanCommand>
wabt::Result OnAssertReturnNanCommand(const NanCommand*);
wabt::Result OnAssertTrapCommand(const AssertTrapCommand*);
wabt::Result OnAssertExhaustionCommand(const AssertExhaustionCommand*);
void TallyCommand(wabt::Result);
wabt::Result ReadInvalidTextModule(string_view module_filename,
Environment* env,
const std::string& header);
wabt::Result ReadInvalidModule(int line_number,
string_view module_filename,
Environment* env,
ModuleType module_type,
const char* desc);
Environment env_;
Executor executor_;
DefinedModule* last_module_ = nullptr;
int passed_ = 0;
int total_ = 0;
std::string source_filename_;
};
static interp::Result PrintCallback(const HostFunc* func,
const interp::FuncSignature* sig,
const TypedValues& args,
TypedValues& results) {
printf("called host ");
WriteCall(s_stdout_stream.get(), func->module_name, func->field_name, args,
results, interp::ResultType::Ok);
return interp::ResultType::Ok;
}
static void InitEnvironment(Environment* env) {
HostModule* host_module = env->AppendHostModule("spectest");
host_module->AppendFuncExport("print", {{}, {}}, PrintCallback);
host_module->AppendFuncExport("print_i32", {{Type::I32}, {}}, PrintCallback);
host_module->AppendFuncExport("print_f32", {{Type::F32}, {}}, PrintCallback);
host_module->AppendFuncExport("print_f64", {{Type::F64}, {}}, PrintCallback);
host_module->AppendFuncExport("print_i32_f32", {{Type::I32, Type::F32}, {}},
PrintCallback);
host_module->AppendFuncExport("print_f64_f64", {{Type::F64, Type::F64}, {}},
PrintCallback);
host_module->AppendTableExport("table", Type::Funcref, Limits(10, 20));
host_module->AppendMemoryExport("memory", Limits(1, 2));
host_module->AppendGlobalExport("global_i32", false, uint32_t(666));
host_module->AppendGlobalExport("global_i64", false, uint64_t(666));
host_module->AppendGlobalExport("global_f32", false, float(666.6f));
host_module->AppendGlobalExport("global_f64", false, double(666.6));
}
CommandRunner::CommandRunner()
: executor_(&env_, s_trace_stream, s_thread_options) {
InitEnvironment(&env_);
}
wabt::Result CommandRunner::Run(const Script& script) {
source_filename_ = script.filename;
for (const CommandPtr& command : script.commands) {
switch (command->type) {
case CommandType::Module:
OnModuleCommand(cast<ModuleCommand>(command.get()));
break;
case CommandType::Action:
TallyCommand(OnActionCommand(cast<ActionCommand>(command.get())));
break;
case CommandType::Register:
OnRegisterCommand(cast<RegisterCommand>(command.get()));
break;
case CommandType::AssertMalformed:
TallyCommand(OnAssertMalformedCommand(
cast<AssertMalformedCommand>(command.get())));
break;
case CommandType::AssertInvalid:
TallyCommand(
OnAssertInvalidCommand(cast<AssertInvalidCommand>(command.get())));
break;
case CommandType::AssertUnlinkable:
TallyCommand(OnAssertUnlinkableCommand(
cast<AssertUnlinkableCommand>(command.get())));
break;
case CommandType::AssertUninstantiable:
TallyCommand(OnAssertUninstantiableCommand(
cast<AssertUninstantiableCommand>(command.get())));
break;
case CommandType::AssertReturn:
TallyCommand(
OnAssertReturnCommand(cast<AssertReturnCommand>(command.get())));
break;
case CommandType::AssertReturnFunc:
TallyCommand(
OnAssertReturnFuncCommand(cast<AssertReturnFuncCommand>(command.get())));
break;
case CommandType::AssertReturnCanonicalNan:
TallyCommand(OnAssertReturnNanCommand(
cast<AssertReturnCanonicalNanCommand>(command.get())));
break;
case CommandType::AssertReturnArithmeticNan:
TallyCommand(OnAssertReturnNanCommand(
cast<AssertReturnArithmeticNanCommand>(command.get())));
break;
case CommandType::AssertTrap:
TallyCommand(
OnAssertTrapCommand(cast<AssertTrapCommand>(command.get())));
break;
case CommandType::AssertExhaustion:
TallyCommand(OnAssertExhaustionCommand(
cast<AssertExhaustionCommand>(command.get())));
break;
}
}
return wabt::Result::Ok;
}
void CommandRunner::PrintError(uint32_t line_number, const char* format, ...) {
WABT_SNPRINTF_ALLOCA(buffer, length, format);
printf("%s:%u: %s\n", source_filename_.c_str(), line_number, buffer);
}
static ExecResult GetGlobalExportByName(Environment* env,
interp::Module* module,
string_view name) {
interp::Export* export_ = module->GetExport(name);
if (!export_) {
return ExecResult(interp::ResultType::UnknownExport);
}
if (export_->kind != ExternalKind::Global) {
return ExecResult(interp::ResultType::ExportKindMismatch);
}
interp::Global* global = env->GetGlobal(export_->index);
return ExecResult(interp::ResultType::Ok, {global->typed_value});
}
ExecResult CommandRunner::RunAction(int line_number,
const Action* action,
RunVerbosity verbose) {
interp::Module* module;
if (!action->module_name.empty()) {
module = env_.FindModule(action->module_name);
} else {
module = env_.GetLastModule();
}
assert(module);
ExecResult exec_result;
switch (action->type) {
case ActionType::Invoke:
exec_result =
executor_.RunExportByName(module, action->field_name, action->args);
if (verbose == RunVerbosity::Verbose) {
WriteCall(s_stdout_stream.get(), string_view(), action->field_name,
action->args, exec_result.values, exec_result.result);
}
break;
case ActionType::Get:
exec_result = GetGlobalExportByName(&env_, module, action->field_name);
break;
default:
WABT_UNREACHABLE;
}
return exec_result;
}
wabt::Result CommandRunner::ReadInvalidTextModule(string_view module_filename,
Environment* env,
const std::string& header) {
std::vector<uint8_t> file_data;
wabt::Result result = ReadFile(module_filename, &file_data);
std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer(
module_filename, file_data.data(), file_data.size());
Errors errors;
if (Succeeded(result)) {
std::unique_ptr<::Script> script;
WastParseOptions options(s_features);
result = ParseWastScript(lexer.get(), &script, &errors, &options);
if (Succeeded(result)) {
wabt::Module* module = script->GetFirstModule();
result = ResolveNamesModule(module, &errors);
if (Succeeded(result)) {
ValidateOptions options(s_features);
// Don't do a full validation, just validate the function signatures.
result = ValidateFuncSignatures(module, &errors, options);
}
}
}
auto line_finder = lexer->MakeLineFinder();
FormatErrorsToFile(errors, Location::Type::Text, line_finder.get(), stdout,
header, PrintHeader::Once);
return result;
}
static wabt::Result ReadModule(string_view module_filename,
Environment* env,
Errors* errors,
DefinedModule** out_module) {
wabt::Result result;
std::vector<uint8_t> file_data;
*out_module = nullptr;
result = ReadFile(module_filename, &file_data);
if (Succeeded(result)) {
const bool kReadDebugNames = true;
const bool kStopOnFirstError = true;
const bool kFailOnCustomSectionError = true;
ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames,
kStopOnFirstError, kFailOnCustomSectionError);
result = ReadBinaryInterp(env, file_data.data(), file_data.size(), options,
errors, out_module);
if (Succeeded(result)) {
if (s_verbose) {
env->DisassembleModule(s_stdout_stream.get(), *out_module);
}
}
}
return result;
}
wabt::Result CommandRunner::ReadInvalidModule(int line_number,
string_view module_filename,
Environment* env,
ModuleType module_type,
const char* desc) {
std::string header = StringPrintf(
"%s:%d: %s passed", source_filename_.c_str(), line_number, desc);
switch (module_type) {
case ModuleType::Text: {
return ReadInvalidTextModule(module_filename, env, header);
}
case ModuleType::Binary: {
DefinedModule* module;
Errors errors;
wabt::Result result = ReadModule(module_filename, env, &errors, &module);
FormatErrorsToFile(errors, Location::Type::Binary, {}, stdout, header,
PrintHeader::Once);
return result;
}
}
WABT_UNREACHABLE;
}
wabt::Result CommandRunner::OnModuleCommand(const ModuleCommand* command) {
Environment::MarkPoint mark = env_.Mark();
Errors errors;
wabt::Result result = ReadModule(command->filename, &env_,
&errors, &last_module_);
FormatErrorsToFile(errors, Location::Type::Binary);
if (Failed(result)) {
env_.ResetToMarkPoint(mark);
PrintError(command->line, "error reading module: \"%s\"",
command->filename.c_str());
return wabt::Result::Error;
}
ExecResult exec_result = executor_.RunStartFunction(last_module_);
if (!exec_result.ok()) {
env_.ResetToMarkPoint(mark);
WriteResult(s_stdout_stream.get(), "error running start function",
exec_result.result);
return wabt::Result::Error;
}
if (!command->name.empty()) {
last_module_->name = command->name;
env_.EmplaceModuleBinding(command->name,
Binding(env_.GetModuleCount() - 1));
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnActionCommand(const ActionCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Verbose);
if (!exec_result.ok()) {
PrintError(command->line, "unexpected trap: %s",
ResultToString(exec_result.result).c_str());
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertMalformedCommand(
const AssertMalformedCommand* command) {
Environment env;
InitEnvironment(&env);
wabt::Result result =
ReadInvalidModule(command->line, command->filename, &env, command->type,
"assert_malformed");
if (Succeeded(result)) {
PrintError(command->line, "expected module to be malformed: \"%s\"",
command->filename.c_str());
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnRegisterCommand(const RegisterCommand* command) {
Index module_index;
if (!command->name.empty()) {
module_index = env_.FindModuleIndex(command->name);
} else {
module_index = env_.GetLastModuleIndex();
}
if (module_index == kInvalidIndex) {
PrintError(command->line, "unknown module in register");
return wabt::Result::Error;
}
env_.EmplaceRegisteredModuleBinding(command->as, Binding(module_index));
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertUnlinkableCommand(
const AssertUnlinkableCommand* command) {
wabt::Result result =
ReadInvalidModule(command->line, command->filename, &env_, command->type,
"assert_unlinkable");
if (Succeeded(result)) {
PrintError(command->line, "expected module to be unlinkable: \"%s\"",
command->filename.c_str());
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertInvalidCommand(
const AssertInvalidCommand* command) {
Environment env;
InitEnvironment(&env);
wabt::Result result = ReadInvalidModule(
command->line, command->filename, &env, command->type, "assert_invalid");
if (Succeeded(result)) {
PrintError(command->line, "expected module to be invalid: \"%s\"",
command->filename.c_str());
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertUninstantiableCommand(
const AssertUninstantiableCommand* command) {
Errors errors;
DefinedModule* module;
wabt::Result result = ReadModule(command->filename, &env_, &errors, &module);
FormatErrorsToFile(errors, Location::Type::Binary);
if (Succeeded(result)) {
ExecResult exec_result = executor_.RunStartFunction(module);
if (exec_result.ok()) {
PrintError(command->line, "expected error running start function: \"%s\"",
command->filename.c_str());
result = wabt::Result::Error;
} else {
result = wabt::Result::Ok;
}
} else {
PrintError(command->line, "error reading module: \"%s\"",
command->filename.c_str());
result = wabt::Result::Error;
}
// Don't reset env_ here; if the start function fails, the environment is
// still modified. For example, a table may have been populated with a
// function from this module.
return result;
}
static bool TypedValuesAreEqual(const TypedValue& tv1, const TypedValue& 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;
case Type::V128:
return tv1.value.vec128 == tv2.value.vec128;
case Type::Nullref:
return true;
case Type::Funcref:
return tv1.value.ref.index == tv2.value.ref.index;
case Type::Hostref:
return tv1.value.ref.index == tv2.value.ref.index;
default:
WABT_UNREACHABLE;
}
}
wabt::Result CommandRunner::OnAssertReturnFuncCommand(
const AssertReturnFuncCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Quiet);
if (!exec_result.ok()) {
PrintError(command->line, "unexpected trap: %s",
ResultToString(exec_result.result).c_str());
return wabt::Result::Error;
}
if (exec_result.values.size() != 1) {
PrintError(command->line,
"expected 1 result in assert_return_func, got %" PRIzd,
exec_result.values.size());
return wabt::Result::Error;
}
const TypedValue& result = exec_result.values[0];
if (result.type != Type::Funcref) {
PrintError(command->line,
"mismatch in result of assert_return_func: expected %s, got %s",
GetTypeName(Type::Funcref), TypedValueToString(result).c_str());
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertReturnCommand(
const AssertReturnCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Quiet);
if (!exec_result.ok()) {
PrintError(command->line, "unexpected trap: %s",
ResultToString(exec_result.result).c_str());
return wabt::Result::Error;
}
if (exec_result.values.size() != command->expected.size()) {
PrintError(command->line,
"result length mismatch in assert_return: expected %" PRIzd
", got %" PRIzd,
command->expected.size(), exec_result.values.size());
return wabt::Result::Error;
}
wabt::Result result = wabt::Result::Ok;
for (size_t i = 0; i < exec_result.values.size(); ++i) {
const TypedValue& expected_tv = command->expected[i];
const TypedValue& actual_tv = exec_result.values[i];
if (!TypedValuesAreEqual(expected_tv, actual_tv)) {
PrintError(command->line,
"mismatch in result %" PRIzd
" of assert_return: expected %s, got %s",
i, TypedValueToString(expected_tv).c_str(),
TypedValueToString(actual_tv).c_str());
result = wabt::Result::Error;
}
}
return result;
}
template <typename NanCommand>
wabt::Result CommandRunner::OnAssertReturnNanCommand(
const NanCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Quiet);
if (!exec_result.ok()) {
PrintError(command->line, "unexpected trap: %s",
ResultToString(exec_result.result).c_str());
return wabt::Result::Error;
}
if (exec_result.values.size() != 1) {
PrintError(command->line, "expected one result, got %" PRIzd,
exec_result.values.size());
return wabt::Result::Error;
}
const bool is_canonical =
command->type == CommandType::AssertReturnCanonicalNan;
const TypedValue& actual = exec_result.values[0];
switch (actual.type) {
case Type::F32: {
bool is_nan = is_canonical ? IsCanonicalNan(actual.value.f32_bits)
: IsArithmeticNan(actual.value.f32_bits);
if (!is_nan) {
PrintError(command->line, "expected result to be nan, got %s",
TypedValueToString(actual).c_str());
return wabt::Result::Error;
}
break;
}
case Type::F64: {
bool is_nan = is_canonical ? IsCanonicalNan(actual.value.f64_bits)
: IsArithmeticNan(actual.value.f64_bits);
if (!is_nan) {
PrintError(command->line, "expected result to be nan, got %s",
TypedValueToString(actual).c_str());
return wabt::Result::Error;
}
break;
}
default:
PrintError(command->line, "expected result type to be f32 or f64, got %s",
GetTypeName(actual.type));
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertTrapCommand(
const AssertTrapCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Quiet);
if (exec_result.ok()) {
PrintError(command->line, "expected trap: \"%s\"", command->text.c_str());
return wabt::Result::Error;
}
PrintError(command->line, "assert_trap passed: %s",
ResultToString(exec_result.result).c_str());
return wabt::Result::Ok;
}
wabt::Result CommandRunner::OnAssertExhaustionCommand(
const AssertExhaustionCommand* command) {
ExecResult exec_result =
RunAction(command->line, &command->action, RunVerbosity::Quiet);
if (exec_result.result.type != interp::ResultType::TrapCallStackExhausted &&
exec_result.result.type != interp::ResultType::TrapValueStackExhausted) {
PrintError(command->line, "expected call stack exhaustion");
return wabt::Result::Error;
}
return wabt::Result::Ok;
}
void CommandRunner::TallyCommand(wabt::Result result) {
if (Succeeded(result)) {
passed_++;
}
total_++;
}
static int ReadAndRunSpecJSON(string_view spec_json_filename) {
JSONParser parser;
if (parser.ReadFile(spec_json_filename) == wabt::Result::Error) {
return 1;
}
Script script;
if (parser.ParseScript(&script) == wabt::Result::Error) {
return 1;
}
CommandRunner runner;
if (runner.Run(script) == wabt::Result::Error) {
return 1;
}
printf("%d/%d tests passed.\n", runner.passed(), runner.total());
const int failed = runner.total() - runner.passed();
return failed;
}
} // namespace spectest
int ProgramMain(int argc, char** argv) {
InitStdio();
s_stdout_stream = FileStream::CreateStdout();
ParseOptions(argc, argv);
return spectest::ReadAndRunSpecJSON(s_infile);
}
int main(int argc, char** argv) {
WABT_TRY
return ProgramMain(argc, argv);
WABT_CATCH_BAD_ALLOC_AND_EXIT
}