blob: 171b40e3120bdbe288dbb4a0ac41d6dbd36e24ae [file] [log] [blame]
/*
* 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 "binary-writer-spec.h"
#include <assert.h>
#include <inttypes.h>
#include "ast.h"
#include "binary.h"
#include "binary-writer.h"
#include "config.h"
#include "stream.h"
#include "writer.h"
namespace wabt {
static void convert_backslash_to_slash(char* s, size_t length) {
for (size_t i = 0; i < length; ++i)
if (s[i] == '\\')
s[i] = '/';
}
static StringSlice strip_extension(const char* s) {
/* strip .json or .wasm, but leave other extensions, e.g.:
*
* s = "foo", => "foo"
* s = "foo.json" => "foo"
* s = "foo.wasm" => "foo"
* s = "foo.bar" => "foo.bar"
*/
if (!s) {
StringSlice result;
result.start = nullptr;
result.length = 0;
return result;
}
size_t slen = strlen(s);
const char* ext_start = strrchr(s, '.');
if (!ext_start)
ext_start = s + slen;
StringSlice result;
result.start = s;
if (strcmp(ext_start, ".json") == 0 || strcmp(ext_start, ".wasm") == 0) {
result.length = ext_start - s;
} else {
result.length = slen;
}
return result;
}
static StringSlice get_basename(const char* s) {
/* strip everything up to and including the last slash, e.g.:
*
* s = "/foo/bar/baz", => "baz"
* s = "/usr/local/include/stdio.h", => "stdio.h"
* s = "foo.bar", => "foo.bar"
*/
size_t slen = strlen(s);
const char* start = s;
const char* last_slash = strrchr(s, '/');
if (last_slash)
start = last_slash + 1;
StringSlice result;
result.start = start;
result.length = s + slen - start;
return result;
}
namespace {
class BinaryWriterSpec {
public:
BinaryWriterSpec(const char* source_filename,
const WriteBinarySpecOptions* spec_options);
Result WriteScript(Script* script);
private:
char* GetModuleFilename();
void WriteString(const char* s);
void WriteKey(const char* key);
void WriteSeparator();
void WriteEscapedStringSlice(StringSlice ss);
void WriteCommandType(const Command& command);
void WriteLocation(const Location* loc);
void WriteVar(const Var* var);
void WriteTypeObject(Type type);
void WriteConst(const Const* const_);
void WriteConstVector(const ConstVector& consts);
void WriteAction(const Action* action);
void WriteActionResultType(Script* script, const Action* action);
void WriteModule(char* filename, const Module* module);
void WriteRawModule(char* filename, const RawModule* raw_module);
void WriteInvalidModule(const RawModule* module, StringSlice text);
void WriteCommands(Script* script);
MemoryStream json_stream_;
StringSlice source_filename_;
StringSlice module_filename_noext_;
bool write_modules_ = false; /* Whether to write the modules files. */
const WriteBinarySpecOptions* spec_options_ = nullptr;
Result result_ = Result::Ok;
size_t num_modules_ = 0;
};
BinaryWriterSpec::BinaryWriterSpec(const char* source_filename,
const WriteBinarySpecOptions* spec_options)
: spec_options_(spec_options) {
source_filename_.start = source_filename;
source_filename_.length = strlen(source_filename);
module_filename_noext_ = strip_extension(spec_options_->json_filename
? spec_options_->json_filename
: source_filename);
write_modules_ = !!spec_options_->json_filename;
}
char* BinaryWriterSpec::GetModuleFilename() {
size_t buflen = module_filename_noext_.length + 20;
char* str = new char[buflen];
size_t length = wabt_snprintf(
str, buflen, PRIstringslice ".%" PRIzd ".wasm",
WABT_PRINTF_STRING_SLICE_ARG(module_filename_noext_), num_modules_);
convert_backslash_to_slash(str, length);
return str;
}
void BinaryWriterSpec::WriteString(const char* s) {
json_stream_.Writef("\"%s\"", s);
}
void BinaryWriterSpec::WriteKey(const char* key) {
json_stream_.Writef("\"%s\": ", key);
}
void BinaryWriterSpec::WriteSeparator() {
json_stream_.Writef(", ");
}
void BinaryWriterSpec::WriteEscapedStringSlice(StringSlice ss) {
json_stream_.WriteChar('"');
for (size_t i = 0; i < ss.length; ++i) {
uint8_t c = ss.start[i];
if (c < 0x20 || c == '\\' || c == '"') {
json_stream_.Writef("\\u%04x", c);
} else {
json_stream_.WriteChar(c);
}
}
json_stream_.WriteChar('"');
}
void BinaryWriterSpec::WriteCommandType(const Command& command) {
static const char* s_command_names[] = {
"module",
"action",
"register",
"assert_malformed",
"assert_invalid",
nullptr, /* ASSERT_INVALID_NON_BINARY, this command will never be
written */
"assert_unlinkable",
"assert_uninstantiable",
"assert_return",
"assert_return_canonical_nan",
"assert_return_arithmetic_nan",
"assert_trap",
"assert_exhaustion",
};
WABT_STATIC_ASSERT(WABT_ARRAY_SIZE(s_command_names) == kCommandTypeCount);
WriteKey("type");
assert(s_command_names[static_cast<size_t>(command.type)]);
WriteString(s_command_names[static_cast<size_t>(command.type)]);
}
void BinaryWriterSpec::WriteLocation(const Location* loc) {
WriteKey("line");
json_stream_.Writef("%d", loc->line);
}
void BinaryWriterSpec::WriteVar(const Var* var) {
if (var->type == VarType::Index)
json_stream_.Writef("\"%" PRIu64 "\"", var->index);
else
WriteEscapedStringSlice(var->name);
}
void BinaryWriterSpec::WriteTypeObject(Type type) {
json_stream_.Writef("{");
WriteKey("type");
WriteString(get_type_name(type));
json_stream_.Writef("}");
}
void BinaryWriterSpec::WriteConst(const Const* const_) {
json_stream_.Writef("{");
WriteKey("type");
/* Always write the values as strings, even though they may be representable
* as JSON numbers. This way the formatting is consistent. */
switch (const_->type) {
case Type::I32:
WriteString("i32");
WriteSeparator();
WriteKey("value");
json_stream_.Writef("\"%u\"", const_->u32);
break;
case Type::I64:
WriteString("i64");
WriteSeparator();
WriteKey("value");
json_stream_.Writef("\"%" PRIu64 "\"", const_->u64);
break;
case Type::F32: {
/* TODO(binji): write as hex float */
WriteString("f32");
WriteSeparator();
WriteKey("value");
json_stream_.Writef("\"%u\"", const_->f32_bits);
break;
}
case Type::F64: {
/* TODO(binji): write as hex float */
WriteString("f64");
WriteSeparator();
WriteKey("value");
json_stream_.Writef("\"%" PRIu64 "\"", const_->f64_bits);
break;
}
default:
assert(0);
}
json_stream_.Writef("}");
}
void BinaryWriterSpec::WriteConstVector(const ConstVector& consts) {
json_stream_.Writef("[");
for (size_t i = 0; i < consts.size(); ++i) {
const Const* const_ = &consts[i];
WriteConst(const_);
if (i != consts.size() - 1)
WriteSeparator();
}
json_stream_.Writef("]");
}
void BinaryWriterSpec::WriteAction(const Action* action) {
WriteKey("action");
json_stream_.Writef("{");
WriteKey("type");
if (action->type == ActionType::Invoke) {
WriteString("invoke");
} else {
assert(action->type == ActionType::Get);
WriteString("get");
}
WriteSeparator();
if (action->module_var.type != VarType::Index) {
WriteKey("module");
WriteVar(&action->module_var);
WriteSeparator();
}
if (action->type == ActionType::Invoke) {
WriteKey("field");
WriteEscapedStringSlice(action->name);
WriteSeparator();
WriteKey("args");
WriteConstVector(action->invoke->args);
} else {
WriteKey("field");
WriteEscapedStringSlice(action->name);
}
json_stream_.Writef("}");
}
void BinaryWriterSpec::WriteActionResultType(Script* script,
const Action* action) {
const Module* module = get_module_by_var(script, &action->module_var);
const Export* export_;
json_stream_.Writef("[");
switch (action->type) {
case ActionType::Invoke: {
export_ = get_export_by_name(module, &action->name);
assert(export_->kind == ExternalKind::Func);
Func* func = get_func_by_var(module, &export_->var);
size_t num_results = get_num_results(func);
for (size_t i = 0; i < num_results; ++i)
WriteTypeObject(get_result_type(func, i));
break;
}
case ActionType::Get: {
export_ = get_export_by_name(module, &action->name);
assert(export_->kind == ExternalKind::Global);
Global* global = get_global_by_var(module, &export_->var);
WriteTypeObject(global->type);
break;
}
}
json_stream_.Writef("]");
}
void BinaryWriterSpec::WriteModule(char* filename, const Module* module) {
MemoryStream memory_stream;
result_ = write_binary_module(&memory_stream.writer(), module,
&spec_options_->write_binary_options);
if (WABT_SUCCEEDED(result_) && write_modules_)
result_ = memory_stream.WriteToFile(filename);
}
void BinaryWriterSpec::WriteRawModule(char* filename,
const RawModule* raw_module) {
if (raw_module->type == RawModuleType::Text) {
WriteModule(filename, raw_module->text);
} else if (write_modules_) {
FileStream file_stream(filename);
if (file_stream.is_open()) {
file_stream.WriteData(raw_module->binary.data, raw_module->binary.size,
"");
result_ = file_stream.result();
} else {
result_ = Result::Error;
}
}
}
void BinaryWriterSpec::WriteInvalidModule(const RawModule* module,
StringSlice text) {
char* filename = GetModuleFilename();
WriteLocation(get_raw_module_location(module));
WriteSeparator();
WriteKey("filename");
WriteEscapedStringSlice(get_basename(filename));
WriteSeparator();
WriteKey("text");
WriteEscapedStringSlice(text);
WriteRawModule(filename, module);
delete [] filename;
}
void BinaryWriterSpec::WriteCommands(Script* script) {
json_stream_.Writef("{\"source_filename\": ");
WriteEscapedStringSlice(source_filename_);
json_stream_.Writef(",\n \"commands\": [\n");
int last_module_index = -1;
for (size_t i = 0; i < script->commands.size(); ++i) {
const Command& command = *script->commands[i].get();
if (command.type == CommandType::AssertInvalidNonBinary)
continue;
if (i != 0)
WriteSeparator();
json_stream_.Writef("\n");
json_stream_.Writef(" {");
WriteCommandType(command);
WriteSeparator();
switch (command.type) {
case CommandType::Module: {
Module* module = command.module;
char* filename = GetModuleFilename();
WriteLocation(&module->loc);
WriteSeparator();
if (module->name.start) {
WriteKey("name");
WriteEscapedStringSlice(module->name);
WriteSeparator();
}
WriteKey("filename");
WriteEscapedStringSlice(get_basename(filename));
WriteModule(filename, module);
delete [] filename;
num_modules_++;
last_module_index = static_cast<int>(i);
break;
}
case CommandType::Action:
WriteLocation(&command.action->loc);
WriteSeparator();
WriteAction(command.action);
break;
case CommandType::Register:
WriteLocation(&command.register_.var.loc);
WriteSeparator();
if (command.register_.var.type == VarType::Name) {
WriteKey("name");
WriteVar(&command.register_.var);
WriteSeparator();
} else {
/* If we're not registering by name, then we should only be
* registering the last module. */
WABT_USE(last_module_index);
assert(command.register_.var.index == last_module_index);
}
WriteKey("as");
WriteEscapedStringSlice(command.register_.module_name);
break;
case CommandType::AssertMalformed:
WriteInvalidModule(command.assert_malformed.module,
command.assert_malformed.text);
num_modules_++;
break;
case CommandType::AssertInvalid:
WriteInvalidModule(command.assert_invalid.module,
command.assert_invalid.text);
num_modules_++;
break;
case CommandType::AssertUnlinkable:
WriteInvalidModule(command.assert_unlinkable.module,
command.assert_unlinkable.text);
num_modules_++;
break;
case CommandType::AssertUninstantiable:
WriteInvalidModule(command.assert_uninstantiable.module,
command.assert_uninstantiable.text);
num_modules_++;
break;
case CommandType::AssertReturn:
WriteLocation(&command.assert_return.action->loc);
WriteSeparator();
WriteAction(command.assert_return.action);
WriteSeparator();
WriteKey("expected");
WriteConstVector(*command.assert_return.expected);
break;
case CommandType::AssertReturnCanonicalNan:
WriteLocation(&command.assert_return_canonical_nan.action->loc);
WriteSeparator();
WriteAction(command.assert_return_canonical_nan.action);
WriteSeparator();
WriteKey("expected");
WriteActionResultType(script,
command.assert_return_canonical_nan.action);
break;
case CommandType::AssertReturnArithmeticNan:
WriteLocation(&command.assert_return_arithmetic_nan.action->loc);
WriteSeparator();
WriteAction(command.assert_return_arithmetic_nan.action);
WriteSeparator();
WriteKey("expected");
WriteActionResultType(script,
command.assert_return_arithmetic_nan.action);
break;
case CommandType::AssertTrap:
WriteLocation(&command.assert_trap.action->loc);
WriteSeparator();
WriteAction(command.assert_trap.action);
WriteSeparator();
WriteKey("text");
WriteEscapedStringSlice(command.assert_trap.text);
break;
case CommandType::AssertExhaustion:
WriteLocation(&command.assert_trap.action->loc);
WriteSeparator();
WriteAction(command.assert_trap.action);
break;
case CommandType::AssertInvalidNonBinary:
assert(0);
break;
}
json_stream_.Writef("}");
}
json_stream_.Writef("]}\n");
}
Result BinaryWriterSpec::WriteScript(Script* script) {
WriteCommands(script);
if (spec_options_->json_filename) {
json_stream_.WriteToFile(spec_options_->json_filename);
}
return result_;
}
} // namespace
Result write_binary_spec_script(Script* script,
const char* source_filename,
const WriteBinarySpecOptions* spec_options) {
assert(source_filename);
BinaryWriterSpec binary_writer_spec(source_filename, spec_options);
return binary_writer_spec.WriteScript(script);
}
} // namespace wabt