/* * 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 "wabt/binary-writer-spec.h" #include #include #include #include "wabt/config.h" #include "wabt/binary-writer.h" #include "wabt/binary.h" #include "wabt/cast.h" #include "wabt/filenames.h" #include "wabt/ir.h" #include "wabt/literal.h" #include "wabt/stream.h" namespace wabt { namespace { class BinaryWriterSpec { public: BinaryWriterSpec(Stream* json_stream, WriteBinarySpecStreamFactory module_stream_factory, std::string_view source_filename, std::string_view module_filename_noext, const WriteBinaryOptions& options); Result WriteScript(const Script& script); private: std::string GetModuleFilename(const char* extension); void WriteString(const char* s); void WriteKey(const char* key); void WriteSeparator(); void WriteEscapedString(std::string_view); void WriteCommandType(const Command& command); void WriteLocation(const Location& loc); void WriteVar(const Var& var); void WriteTypeObject(Type type); void WriteF32(uint32_t, ExpectedNan); void WriteF64(uint64_t, ExpectedNan); void WriteRefBits(uintptr_t ref_bits); void WriteConst(const Const& const_); void WriteConstVector(const ConstVector& consts); void WriteAction(const Action& action); void WriteActionResultType(const Action& action); void WriteModule(std::string_view filename, const Module& module); void WriteScriptModule(std::string_view filename, const ScriptModule& script_module); void WriteInvalidModule(const ScriptModule& module, std::string_view text); void WriteCommands(); const Script* script_ = nullptr; Stream* json_stream_ = nullptr; WriteBinarySpecStreamFactory module_stream_factory_; std::string source_filename_; std::string module_filename_noext_; const WriteBinaryOptions& options_; Result result_ = Result::Ok; size_t num_modules_ = 0; }; BinaryWriterSpec::BinaryWriterSpec( Stream* json_stream, WriteBinarySpecStreamFactory module_stream_factory, std::string_view source_filename, std::string_view module_filename_noext, const WriteBinaryOptions& options) : json_stream_(json_stream), module_stream_factory_(module_stream_factory), source_filename_(source_filename), module_filename_noext_(module_filename_noext), options_(options) {} std::string BinaryWriterSpec::GetModuleFilename(const char* extension) { std::string result = module_filename_noext_; result += '.'; result += std::to_string(num_modules_); result += extension; ConvertBackslashToSlash(&result); return result; } 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::WriteEscapedString(std::string_view s) { json_stream_->WriteChar('"'); for (size_t i = 0; i < s.length(); ++i) { uint8_t c = s[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", "module", "action", "register", "assert_malformed", "assert_invalid", "assert_unlinkable", "assert_uninstantiable", "assert_return", "assert_trap", "assert_exhaustion", "assert_exception", }; WABT_STATIC_ASSERT(WABT_ARRAY_SIZE(s_command_names) == kCommandTypeCount); WriteKey("type"); assert(s_command_names[static_cast(command.type)]); WriteString(s_command_names[static_cast(command.type)]); } void BinaryWriterSpec::WriteLocation(const Location& loc) { WriteKey("line"); json_stream_->Writef("%d", loc.line); } void BinaryWriterSpec::WriteVar(const Var& var) { if (var.is_index()) { json_stream_->Writef("\"%" PRIindex "\"", var.index()); } else { WriteEscapedString(var.name()); } } void BinaryWriterSpec::WriteTypeObject(Type type) { json_stream_->Writef("{"); WriteKey("type"); WriteString(type.GetName().c_str()); json_stream_->Writef("}"); } void BinaryWriterSpec::WriteF32(uint32_t f32_bits, ExpectedNan expected) { switch (expected) { case ExpectedNan::None: json_stream_->Writef("\"%u\"", f32_bits); break; case ExpectedNan::Arithmetic: WriteString("nan:arithmetic"); break; case ExpectedNan::Canonical: WriteString("nan:canonical"); break; } } void BinaryWriterSpec::WriteF64(uint64_t f64_bits, ExpectedNan expected) { switch (expected) { case ExpectedNan::None: json_stream_->Writef("\"%" PRIu64 "\"", f64_bits); break; case ExpectedNan::Arithmetic: WriteString("nan:arithmetic"); break; case ExpectedNan::Canonical: WriteString("nan:canonical"); break; } } void BinaryWriterSpec::WriteRefBits(uintptr_t ref_bits) { if (ref_bits == Const::kRefNullBits) { json_stream_->Writef("\"null\""); } else { json_stream_->Writef("\"%" PRIuPTR "\"", ref_bits); } } 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: WriteString("f32"); WriteSeparator(); WriteKey("value"); WriteF32(const_.f32_bits(), const_.expected_nan()); break; case Type::F64: WriteString("f64"); WriteSeparator(); WriteKey("value"); WriteF64(const_.f64_bits(), const_.expected_nan()); break; case Type::FuncRef: { WriteString("funcref"); WriteSeparator(); WriteKey("value"); WriteRefBits(const_.ref_bits()); break; } case Type::ExternRef: { WriteString("externref"); WriteSeparator(); WriteKey("value"); WriteRefBits(const_.ref_bits()); break; } case Type::V128: { WriteString("v128"); WriteSeparator(); WriteKey("lane_type"); WriteString(const_.lane_type().GetName().c_str()); WriteSeparator(); WriteKey("value"); json_stream_->Writef("["); for (int lane = 0; lane < const_.lane_count(); ++lane) { switch (const_.lane_type()) { case Type::I8: json_stream_->Writef("\"%u\"", const_.v128_lane(lane)); break; case Type::I16: json_stream_->Writef("\"%u\"", const_.v128_lane(lane)); break; case Type::I32: json_stream_->Writef("\"%u\"", const_.v128_lane(lane)); break; case Type::I64: json_stream_->Writef("\"%" PRIu64 "\"", const_.v128_lane(lane)); break; case Type::F32: WriteF32(const_.v128_lane(lane), const_.expected_nan(lane)); break; case Type::F64: WriteF64(const_.v128_lane(lane), const_.expected_nan(lane)); break; default: WABT_UNREACHABLE; } if (lane != const_.lane_count() - 1) { WriteSeparator(); } } json_stream_->Writef("]"); break; } default: WABT_UNREACHABLE; } 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.is_name()) { WriteKey("module"); WriteVar(action.module_var); WriteSeparator(); } if (action.type() == ActionType::Invoke) { WriteKey("field"); WriteEscapedString(action.name); WriteSeparator(); WriteKey("args"); WriteConstVector(cast(&action)->args); } else { WriteKey("field"); WriteEscapedString(action.name); } json_stream_->Writef("}"); } void BinaryWriterSpec::WriteActionResultType(const Action& action) { const Module* module = script_->GetModule(action.module_var); const Export* export_; json_stream_->Writef("["); switch (action.type()) { case ActionType::Invoke: { export_ = module->GetExport(action.name); assert(export_->kind == ExternalKind::Func); const Func* func = module->GetFunc(export_->var); Index num_results = func->GetNumResults(); for (Index i = 0; i < num_results; ++i) WriteTypeObject(func->GetResultType(i)); break; } case ActionType::Get: { export_ = module->GetExport(action.name); assert(export_->kind == ExternalKind::Global); const Global* global = module->GetGlobal(export_->var); WriteTypeObject(global->type); break; } } json_stream_->Writef("]"); } void BinaryWriterSpec::WriteModule(std::string_view filename, const Module& module) { result_ |= WriteBinaryModule(module_stream_factory_(filename), &module, options_); } void BinaryWriterSpec::WriteScriptModule(std::string_view filename, const ScriptModule& script_module) { switch (script_module.type()) { case ScriptModuleType::Text: WriteModule(filename, cast(&script_module)->module); break; case ScriptModuleType::Binary: module_stream_factory_(filename)->WriteData( cast(&script_module)->data, ""); break; case ScriptModuleType::Quoted: module_stream_factory_(filename)->WriteData( cast(&script_module)->data, ""); break; } } void BinaryWriterSpec::WriteInvalidModule(const ScriptModule& module, std::string_view text) { const char* extension = ""; const char* module_type = ""; switch (module.type()) { case ScriptModuleType::Text: extension = kWasmExtension; module_type = "binary"; break; case ScriptModuleType::Binary: extension = kWasmExtension; module_type = "binary"; break; case ScriptModuleType::Quoted: extension = kWatExtension; module_type = "text"; break; } WriteLocation(module.location()); WriteSeparator(); std::string filename = GetModuleFilename(extension); WriteKey("filename"); WriteEscapedString(GetBasename(filename)); WriteSeparator(); WriteKey("text"); WriteEscapedString(text); WriteSeparator(); WriteKey("module_type"); WriteString(module_type); WriteScriptModule(filename, module); } void BinaryWriterSpec::WriteCommands() { json_stream_->Writef("{\"source_filename\": "); WriteEscapedString(source_filename_); json_stream_->Writef(",\n \"commands\": [\n"); Index last_module_index = kInvalidIndex; for (size_t i = 0; i < script_->commands.size(); ++i) { const Command* command = script_->commands[i].get(); if (i != 0) { WriteSeparator(); json_stream_->Writef("\n"); } json_stream_->Writef(" {"); WriteCommandType(*command); WriteSeparator(); switch (command->type) { case CommandType::Module: { const Module& module = cast(command)->module; std::string filename = GetModuleFilename(kWasmExtension); WriteLocation(module.loc); WriteSeparator(); if (!module.name.empty()) { WriteKey("name"); WriteEscapedString(module.name); WriteSeparator(); } WriteKey("filename"); WriteEscapedString(GetBasename(filename)); WriteModule(filename, module); num_modules_++; last_module_index = i; break; } case CommandType::ScriptModule: { auto* script_module_command = cast(command); const auto& module = script_module_command->module; std::string filename = GetModuleFilename(kWasmExtension); WriteLocation(module.loc); WriteSeparator(); if (!module.name.empty()) { WriteKey("name"); WriteEscapedString(module.name); WriteSeparator(); } WriteKey("filename"); WriteEscapedString(GetBasename(filename)); WriteScriptModule(filename, *script_module_command->script_module); num_modules_++; last_module_index = i; break; } case CommandType::Action: { const Action& action = *cast(command)->action; WriteLocation(action.loc); WriteSeparator(); WriteAction(action); WriteSeparator(); WriteKey("expected"); WriteActionResultType(action); break; } case CommandType::Register: { auto* register_command = cast(command); const Var& var = register_command->var; WriteLocation(var.loc); WriteSeparator(); if (var.is_name()) { WriteKey("name"); WriteVar(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(var.index() == last_module_index); } WriteKey("as"); WriteEscapedString(register_command->module_name); break; } case CommandType::AssertMalformed: { auto* assert_malformed_command = cast(command); WriteInvalidModule(*assert_malformed_command->module, assert_malformed_command->text); num_modules_++; break; } case CommandType::AssertInvalid: { auto* assert_invalid_command = cast(command); WriteInvalidModule(*assert_invalid_command->module, assert_invalid_command->text); num_modules_++; break; } case CommandType::AssertUnlinkable: { auto* assert_unlinkable_command = cast(command); WriteInvalidModule(*assert_unlinkable_command->module, assert_unlinkable_command->text); num_modules_++; break; } case CommandType::AssertUninstantiable: { auto* assert_uninstantiable_command = cast(command); WriteInvalidModule(*assert_uninstantiable_command->module, assert_uninstantiable_command->text); num_modules_++; break; } case CommandType::AssertReturn: { auto* assert_return_command = cast(command); WriteLocation(assert_return_command->action->loc); WriteSeparator(); WriteAction(*assert_return_command->action); WriteSeparator(); const Expectation* expectation = assert_return_command->expected.get(); switch (expectation->type()) { case ExpectationType::Values: WriteKey("expected"); break; case ExpectationType::Either: WriteKey("either"); break; } WriteConstVector(expectation->expected); break; } case CommandType::AssertTrap: { auto* assert_trap_command = cast(command); WriteLocation(assert_trap_command->action->loc); WriteSeparator(); WriteAction(*assert_trap_command->action); WriteSeparator(); WriteKey("text"); WriteEscapedString(assert_trap_command->text); WriteSeparator(); WriteKey("expected"); WriteActionResultType(*assert_trap_command->action); break; } case CommandType::AssertExhaustion: { auto* assert_exhaustion_command = cast(command); WriteLocation(assert_exhaustion_command->action->loc); WriteSeparator(); WriteAction(*assert_exhaustion_command->action); WriteSeparator(); WriteKey("text"); WriteEscapedString(assert_exhaustion_command->text); WriteSeparator(); WriteKey("expected"); WriteActionResultType(*assert_exhaustion_command->action); break; } case CommandType::AssertException: { auto* assert_exception_command = cast(command); WriteLocation(assert_exception_command->action->loc); WriteSeparator(); WriteAction(*assert_exception_command->action); WriteSeparator(); WriteKey("expected"); WriteActionResultType(*assert_exception_command->action); break; } } json_stream_->Writef("}"); } json_stream_->Writef("]}\n"); } Result BinaryWriterSpec::WriteScript(const Script& script) { script_ = &script; WriteCommands(); return result_; } } // end anonymous namespace Result WriteBinarySpecScript(Stream* json_stream, WriteBinarySpecStreamFactory module_stream_factory, Script* script, std::string_view source_filename, std::string_view module_filename_noext, const WriteBinaryOptions& options) { BinaryWriterSpec binary_writer_spec(json_stream, module_stream_factory, source_filename, module_filename_noext, options); return binary_writer_spec.WriteScript(*script); } Result WriteBinarySpecScript( Stream* json_stream, Script* script, std::string_view source_filename, std::string_view module_filename_noext, const WriteBinaryOptions& options, std::vector* out_module_streams, Stream* log_stream) { WriteBinarySpecStreamFactory module_stream_factory = [&](std::string_view filename) { out_module_streams->emplace_back( filename, std::make_unique(log_stream)); return out_module_streams->back().stream.get(); }; BinaryWriterSpec binary_writer_spec(json_stream, module_stream_factory, source_filename, module_filename_noext, options); return binary_writer_spec.WriteScript(*script); } } // namespace wabt