| /* |
| * 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 |