/* * 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/wat-writer.h" #include #include #include #include #include #include #include #include #include #include #include "wabt/cast.h" #include "wabt/common.h" #include "wabt/expr-visitor.h" #include "wabt/ir-util.h" #include "wabt/ir.h" #include "wabt/literal.h" #include "wabt/stream.h" #define WABT_TRACING 0 #include "wabt/tracing.h" #define INDENT_SIZE 2 #define NO_FORCE_NEWLINE 0 #define FORCE_NEWLINE 1 namespace wabt { namespace { static const uint8_t s_is_char_escaped[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // This table matches the characters allowed by wast-lexer.cc for `symbol`. // The disallowed printable characters are: "(),;[]{} and . static const uint8_t s_valid_name_chars[256] = { // 0 1 2 3 4 5 6 7 8 9 a b c d e f /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, }; enum class NextChar { None, Space, Newline, ForceNewline, }; struct ExprTree { explicit ExprTree(const Expr* expr, Index result_count) : expr(expr), result_count(result_count) {} const Expr* expr; std::vector children; Index result_count; }; class WatWriter : ModuleContext { public: WatWriter(Stream* stream, const WriteWatOptions& options, const Module& module) : ModuleContext(module), options_(options), stream_(stream) {} Result WriteModule(); private: void Indent(); void Dedent(); void WriteIndent(); void WriteNextChar(); void WriteDataWithNextChar(const void* src, size_t size); void Writef(const char* format, ...); void WritePutc(char c); void WritePuts(const char* s, NextChar next_char); void WritePutsSpace(const char* s); void WritePutsNewline(const char* s); void WriteNewline(bool force); void WriteOpen(const char* name, NextChar next_char); void WriteOpenNewline(const char* name); void WriteOpenSpace(const char* name); void WriteClose(NextChar next_char); void WriteCloseNewline(); void WriteCloseSpace(); void WriteString(const std::string& str, NextChar next_char); void WriteName(std::string_view str, NextChar next_char); void WriteNameOrIndex(std::string_view str, Index index, NextChar next_char); void WriteQuotedData(const void* data, size_t length); void WriteQuotedString(std::string_view str, NextChar next_char); void WriteVar(const Var& var, NextChar next_char); void WriteVarUnlessZero(const Var& var, NextChar next_char); void WriteMemoryVarUnlessZero(const Var& memidx, NextChar next_char); void WriteTwoMemoryVarsUnlessBothZero(const Var& srcmemidx, const Var& destmemidx, NextChar next_char); void WriteBrVar(const Var& var, NextChar next_char); void WriteRefKind(Type type, NextChar next_char); void WriteType(Type type, NextChar next_char); void WriteTypes(const TypeVector& types, const char* name); void WriteFuncSigSpace(const FuncSignature& func_sig); void WriteBeginBlock(LabelType label_type, const Block& block, const char* text); void WriteEndBlock(); void WriteConst(const Const& const_); void WriteExpr(const Expr* expr); template void WriteLoadStoreExpr(const Expr* expr); template void WriteMemoryLoadStoreExpr(const Expr* expr); void WriteExprList(const ExprList& exprs); void WriteInitExpr(const ExprList& expr); template void WriteTypeBindings(const char* prefix, const T& types, const std::vector& index_to_name, Index binding_index_offset = 0); void WriteBeginFunc(const Func& func); void WriteFunc(const Func& func); void WriteBeginGlobal(const Global& global); void WriteGlobal(const Global& global); void WriteTag(const Tag& tag); void WriteLimits(const Limits& limits); void WriteTable(const Table& table); void WriteElemSegment(const ElemSegment& segment); void WriteMemory(const Memory& memory); void WriteDataSegment(const DataSegment& segment); void WriteImport(const Import& import); void WriteExport(const Export& export_); void WriteTypeEntry(const TypeEntry& type); void WriteField(const Field& field); void WriteStartFunction(const Var& start); class ExprVisitorDelegate; void PushExpr(const Expr* expr, Index operand_count, Index result_count); void FlushExprTree(const ExprTree& expr_tree); void FlushExprTreeVector(const std::vector&); void FlushExprTreeStack(); void WriteFoldedExpr(const Expr*); void WriteFoldedExprList(const ExprList&); void BuildInlineExportMap(); void WriteInlineExports(ExternalKind, Index); bool IsInlineExport(const Export& export_); void BuildInlineImportMap(); void WriteInlineImport(ExternalKind, Index); const WriteWatOptions& options_; Stream* stream_ = nullptr; Result result_ = Result::Ok; int indent_ = 0; NextChar next_char_ = NextChar::None; std::vector expr_tree_stack_; std::multimap, const Export*> inline_export_map_; std::vector inline_import_map_[kExternalKindCount]; Index func_index_ = 0; Index global_index_ = 0; Index table_index_ = 0; Index memory_index_ = 0; Index type_index_ = 0; Index tag_index_ = 0; Index data_segment_index_ = 0; Index elem_segment_index_ = 0; }; void WatWriter::Indent() { indent_ += INDENT_SIZE; } void WatWriter::Dedent() { indent_ -= INDENT_SIZE; assert(indent_ >= 0); } void WatWriter::WriteIndent() { static char s_indent[] = " " " "; static size_t s_indent_len = sizeof(s_indent) - 1; size_t to_write = indent_; while (to_write >= s_indent_len) { stream_->WriteData(s_indent, s_indent_len); to_write -= s_indent_len; } if (to_write > 0) { stream_->WriteData(s_indent, to_write); } } void WatWriter::WriteNextChar() { switch (next_char_) { case NextChar::Space: stream_->WriteChar(' '); break; case NextChar::Newline: case NextChar::ForceNewline: stream_->WriteChar('\n'); WriteIndent(); break; case NextChar::None: break; } next_char_ = NextChar::None; } void WatWriter::WriteDataWithNextChar(const void* src, size_t size) { WriteNextChar(); stream_->WriteData(src, size); } void WABT_PRINTF_FORMAT(2, 3) WatWriter::Writef(const char* format, ...) { WABT_SNPRINTF_ALLOCA(buffer, length, format); /* default to following space */ WriteDataWithNextChar(buffer, length); next_char_ = NextChar::Space; } void WatWriter::WritePutc(char c) { stream_->WriteChar(c); } void WatWriter::WritePuts(const char* s, NextChar next_char) { size_t len = strlen(s); WriteDataWithNextChar(s, len); next_char_ = next_char; } void WatWriter::WritePutsSpace(const char* s) { WritePuts(s, NextChar::Space); } void WatWriter::WritePutsNewline(const char* s) { WritePuts(s, NextChar::Newline); } void WatWriter::WriteNewline(bool force) { if (next_char_ == NextChar::ForceNewline) { WriteNextChar(); } next_char_ = force ? NextChar::ForceNewline : NextChar::Newline; } void WatWriter::WriteOpen(const char* name, NextChar next_char) { WritePuts("(", NextChar::None); WritePuts(name, next_char); Indent(); } void WatWriter::WriteOpenNewline(const char* name) { WriteOpen(name, NextChar::Newline); } void WatWriter::WriteOpenSpace(const char* name) { WriteOpen(name, NextChar::Space); } void WatWriter::WriteClose(NextChar next_char) { if (next_char_ != NextChar::ForceNewline) { next_char_ = NextChar::None; } Dedent(); WritePuts(")", next_char); } void WatWriter::WriteCloseNewline() { WriteClose(NextChar::Newline); } void WatWriter::WriteCloseSpace() { WriteClose(NextChar::Space); } void WatWriter::WriteString(const std::string& str, NextChar next_char) { WritePuts(str.c_str(), next_char); } void WatWriter::WriteName(std::string_view str, NextChar next_char) { // Debug names must begin with a $ for for wast file to be valid assert(!str.empty() && str.front() == '$'); bool has_invalid_chars = std::any_of( str.begin(), str.end(), [](uint8_t c) { return !s_valid_name_chars[c]; }); if (has_invalid_chars) { std::string valid_str; std::transform(str.begin(), str.end(), std::back_inserter(valid_str), [](uint8_t c) { return s_valid_name_chars[c] ? c : '_'; }); WriteDataWithNextChar(valid_str.data(), valid_str.length()); } else { WriteDataWithNextChar(str.data(), str.length()); } next_char_ = next_char; } void WatWriter::WriteNameOrIndex(std::string_view str, Index index, NextChar next_char) { if (!str.empty()) { WriteName(str, next_char); } else { Writef("(;%u;)", index); } } void WatWriter::WriteQuotedData(const void* data, size_t length) { const uint8_t* u8_data = static_cast(data); static const char s_hexdigits[] = "0123456789abcdef"; WriteNextChar(); WritePutc('\"'); for (size_t i = 0; i < length; ++i) { uint8_t c = u8_data[i]; if (s_is_char_escaped[c]) { WritePutc('\\'); WritePutc(s_hexdigits[c >> 4]); WritePutc(s_hexdigits[c & 0xf]); } else { WritePutc(c); } } WritePutc('\"'); next_char_ = NextChar::Space; } void WatWriter::WriteQuotedString(std::string_view str, NextChar next_char) { WriteQuotedData(str.data(), str.length()); next_char_ = next_char; } void WatWriter::WriteVar(const Var& var, NextChar next_char) { if (var.is_index()) { Writef("%" PRIindex, var.index()); next_char_ = next_char; } else { WriteName(var.name(), next_char); } } bool VarIsZero(const Var& var) { return var.is_index() && var.index() == 0; } void WatWriter::WriteVarUnlessZero(const Var& var, NextChar next_char) { if (!VarIsZero(var)) { WriteVar(var, next_char); } } void WatWriter::WriteMemoryVarUnlessZero(const Var& memidx, NextChar next_char) { if (module.GetMemoryIndex(memidx) != 0) { WriteVar(memidx, next_char); } else { next_char_ = next_char; } } void WatWriter::WriteTwoMemoryVarsUnlessBothZero(const Var& srcmemidx, const Var& destmemidx, NextChar next_char) { if (module.GetMemoryIndex(srcmemidx) != 0 || module.GetMemoryIndex(destmemidx) != 0) { WriteVar(srcmemidx, NextChar::Space); WriteVar(destmemidx, next_char); } else { next_char_ = next_char; } } void WatWriter::WriteBrVar(const Var& var, NextChar next_char) { if (var.is_index()) { if (var.index() < GetLabelStackSize()) { Writef("%" PRIindex " (;@%" PRIindex ";)", var.index(), GetLabelStackSize() - var.index() - 1); } else { Writef("%" PRIindex " (; INVALID ;)", var.index()); } next_char_ = next_char; } else { WriteString(var.name(), next_char); } } void WatWriter::WriteRefKind(Type type, NextChar next_char) { WritePuts(type.GetRefKindName(), next_char); } void WatWriter::WriteType(Type type, NextChar next_char) { WritePuts(type.GetName().c_str(), next_char); } void WatWriter::WriteTypes(const TypeVector& types, const char* name) { if (types.size()) { if (name) { WriteOpenSpace(name); } for (Type type : types) { WriteType(type, NextChar::Space); } if (name) { WriteCloseSpace(); } } } void WatWriter::WriteFuncSigSpace(const FuncSignature& func_sig) { WriteTypes(func_sig.param_types, "param"); WriteTypes(func_sig.result_types, "result"); } void WatWriter::WriteBeginBlock(LabelType label_type, const Block& block, const char* text) { WritePutsSpace(text); bool has_label = !block.label.empty(); if (has_label) { WriteString(block.label, NextChar::Space); } WriteTypes(block.decl.sig.param_types, "param"); WriteTypes(block.decl.sig.result_types, "result"); if (!has_label) { Writef(" ;; label = @%" PRIindex, GetLabelStackSize()); } WriteNewline(FORCE_NEWLINE); BeginBlock(label_type, block); Indent(); } void WatWriter::WriteEndBlock() { Dedent(); EndBlock(); WritePutsNewline(Opcode::End_Opcode.GetName()); } void WatWriter::WriteConst(const Const& const_) { switch (const_.type()) { case Type::I32: WritePutsSpace(Opcode::I32Const_Opcode.GetName()); Writef("%d", static_cast(const_.u32())); WriteNewline(NO_FORCE_NEWLINE); break; case Type::I64: WritePutsSpace(Opcode::I64Const_Opcode.GetName()); Writef("%" PRId64, static_cast(const_.u64())); WriteNewline(NO_FORCE_NEWLINE); break; case Type::F32: { WritePutsSpace(Opcode::F32Const_Opcode.GetName()); char buffer[128]; WriteFloatHex(buffer, 128, const_.f32_bits()); WritePutsSpace(buffer); Writef("(;=%g;)", Bitcast(const_.f32_bits())); WriteNewline(NO_FORCE_NEWLINE); break; } case Type::F64: { WritePutsSpace(Opcode::F64Const_Opcode.GetName()); char buffer[128]; WriteDoubleHex(buffer, 128, const_.f64_bits()); WritePutsSpace(buffer); Writef("(;=%g;)", Bitcast(const_.f64_bits())); WriteNewline(NO_FORCE_NEWLINE); break; } case Type::V128: { WritePutsSpace(Opcode::V128Const_Opcode.GetName()); auto vec = const_.vec128(); Writef("i32x4 0x%08x 0x%08x 0x%08x 0x%08x", vec.u32(0), vec.u32(1), vec.u32(2), vec.u32(3)); WriteNewline(NO_FORCE_NEWLINE); break; } default: assert(0); break; } } template void WatWriter::WriteLoadStoreExpr(const Expr* expr) { auto typed_expr = cast(expr); WritePutsSpace(typed_expr->opcode.GetName()); if (typed_expr->offset) { Writef("offset=%" PRIaddress, typed_expr->offset); } if (!typed_expr->opcode.IsNaturallyAligned(typed_expr->align)) { Writef("align=%" PRIaddress, typed_expr->align); } WriteNewline(NO_FORCE_NEWLINE); } template void WatWriter::WriteMemoryLoadStoreExpr(const Expr* expr) { auto typed_expr = cast(expr); WritePutsSpace(typed_expr->opcode.GetName()); WriteMemoryVarUnlessZero(typed_expr->memidx, NextChar::Space); if (typed_expr->offset) { Writef("offset=%" PRIaddress, typed_expr->offset); } if (!typed_expr->opcode.IsNaturallyAligned(typed_expr->align)) { Writef("align=%" PRIaddress, typed_expr->align); } WriteNewline(NO_FORCE_NEWLINE); } class WatWriter::ExprVisitorDelegate : public ExprVisitor::Delegate { public: explicit ExprVisitorDelegate(WatWriter* writer) : writer_(writer) {} Result OnBinaryExpr(BinaryExpr*) override; Result BeginBlockExpr(BlockExpr*) override; Result EndBlockExpr(BlockExpr*) override; Result OnBrExpr(BrExpr*) override; Result OnBrIfExpr(BrIfExpr*) override; Result OnBrTableExpr(BrTableExpr*) override; Result OnCallExpr(CallExpr*) override; Result OnCallIndirectExpr(CallIndirectExpr*) override; Result OnCallRefExpr(CallRefExpr*) override; Result OnCodeMetadataExpr(CodeMetadataExpr*) override; Result OnCompareExpr(CompareExpr*) override; Result OnConstExpr(ConstExpr*) override; Result OnConvertExpr(ConvertExpr*) override; Result OnDropExpr(DropExpr*) override; Result OnGlobalGetExpr(GlobalGetExpr*) override; Result OnGlobalSetExpr(GlobalSetExpr*) override; Result BeginIfExpr(IfExpr*) override; Result AfterIfTrueExpr(IfExpr*) override; Result EndIfExpr(IfExpr*) override; Result OnLoadExpr(LoadExpr*) override; Result OnLocalGetExpr(LocalGetExpr*) override; Result OnLocalSetExpr(LocalSetExpr*) override; Result OnLocalTeeExpr(LocalTeeExpr*) override; Result BeginLoopExpr(LoopExpr*) override; Result EndLoopExpr(LoopExpr*) override; Result OnMemoryCopyExpr(MemoryCopyExpr*) override; Result OnDataDropExpr(DataDropExpr*) override; Result OnMemoryFillExpr(MemoryFillExpr*) override; Result OnMemoryGrowExpr(MemoryGrowExpr*) override; Result OnMemoryInitExpr(MemoryInitExpr*) override; Result OnMemorySizeExpr(MemorySizeExpr*) override; Result OnTableCopyExpr(TableCopyExpr*) override; Result OnElemDropExpr(ElemDropExpr*) override; Result OnTableInitExpr(TableInitExpr*) override; Result OnTableGetExpr(TableGetExpr*) override; Result OnTableSetExpr(TableSetExpr*) override; Result OnTableGrowExpr(TableGrowExpr*) override; Result OnTableSizeExpr(TableSizeExpr*) override; Result OnTableFillExpr(TableFillExpr*) override; Result OnRefFuncExpr(RefFuncExpr*) override; Result OnRefNullExpr(RefNullExpr*) override; Result OnRefIsNullExpr(RefIsNullExpr*) override; Result OnNopExpr(NopExpr*) override; Result OnReturnExpr(ReturnExpr*) override; Result OnReturnCallExpr(ReturnCallExpr*) override; Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr*) override; Result OnSelectExpr(SelectExpr*) override; Result OnStoreExpr(StoreExpr*) override; Result OnUnaryExpr(UnaryExpr*) override; Result OnUnreachableExpr(UnreachableExpr*) override; Result BeginTryExpr(TryExpr*) override; Result OnCatchExpr(TryExpr*, Catch*) override; Result OnDelegateExpr(TryExpr*) override; Result EndTryExpr(TryExpr*) override; Result OnThrowExpr(ThrowExpr*) override; Result OnRethrowExpr(RethrowExpr*) override; Result OnAtomicWaitExpr(AtomicWaitExpr*) override; Result OnAtomicFenceExpr(AtomicFenceExpr*) override; Result OnAtomicNotifyExpr(AtomicNotifyExpr*) override; Result OnAtomicLoadExpr(AtomicLoadExpr*) override; Result OnAtomicStoreExpr(AtomicStoreExpr*) override; Result OnAtomicRmwExpr(AtomicRmwExpr*) override; Result OnAtomicRmwCmpxchgExpr(AtomicRmwCmpxchgExpr*) override; Result OnTernaryExpr(TernaryExpr*) override; Result OnSimdLaneOpExpr(SimdLaneOpExpr*) override; Result OnSimdLoadLaneExpr(SimdLoadLaneExpr*) override; Result OnSimdStoreLaneExpr(SimdStoreLaneExpr*) override; Result OnSimdShuffleOpExpr(SimdShuffleOpExpr*) override; Result OnLoadSplatExpr(LoadSplatExpr*) override; Result OnLoadZeroExpr(LoadZeroExpr*) override; private: WatWriter* writer_; }; Result WatWriter::ExprVisitorDelegate::OnBinaryExpr(BinaryExpr* expr) { writer_->WritePutsNewline(expr->opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::BeginBlockExpr(BlockExpr* expr) { writer_->WriteBeginBlock(LabelType::Block, expr->block, Opcode::Block_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::EndBlockExpr(BlockExpr* expr) { writer_->WriteEndBlock(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnBrExpr(BrExpr* expr) { writer_->WritePutsSpace(Opcode::Br_Opcode.GetName()); writer_->WriteBrVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnBrIfExpr(BrIfExpr* expr) { writer_->WritePutsSpace(Opcode::BrIf_Opcode.GetName()); writer_->WriteBrVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnBrTableExpr(BrTableExpr* expr) { writer_->WritePutsSpace(Opcode::BrTable_Opcode.GetName()); for (const Var& var : expr->targets) { writer_->WriteBrVar(var, NextChar::Space); } writer_->WriteBrVar(expr->default_target, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCallExpr(CallExpr* expr) { writer_->WritePutsSpace(Opcode::Call_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCallIndirectExpr( CallIndirectExpr* expr) { writer_->WritePutsSpace(Opcode::CallIndirect_Opcode.GetName()); writer_->WriteVarUnlessZero(expr->table, NextChar::Space); writer_->WriteOpenSpace("type"); writer_->WriteVar(expr->decl.type_var, NextChar::Newline); writer_->WriteCloseNewline(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCallRefExpr(CallRefExpr* expr) { writer_->WritePutsSpace(Opcode::CallRef_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCodeMetadataExpr( CodeMetadataExpr* expr) { writer_->WriteOpen("@metadata.code.", NextChar::None); writer_->WriteDataWithNextChar(expr->name.data(), expr->name.size()); writer_->WritePutc(' '); writer_->WriteQuotedData(expr->data.data(), expr->data.size()); writer_->WriteCloseSpace(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCompareExpr(CompareExpr* expr) { writer_->WritePutsNewline(expr->opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnConstExpr(ConstExpr* expr) { writer_->WriteConst(expr->const_); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnConvertExpr(ConvertExpr* expr) { writer_->WritePutsNewline(expr->opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnDropExpr(DropExpr* expr) { writer_->WritePutsNewline(Opcode::Drop_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnGlobalGetExpr(GlobalGetExpr* expr) { writer_->WritePutsSpace(Opcode::GlobalGet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnGlobalSetExpr(GlobalSetExpr* expr) { writer_->WritePutsSpace(Opcode::GlobalSet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::BeginIfExpr(IfExpr* expr) { writer_->WriteBeginBlock(LabelType::If, expr->true_, Opcode::If_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::AfterIfTrueExpr(IfExpr* expr) { if (!expr->false_.empty()) { writer_->Dedent(); writer_->WritePutsSpace(Opcode::Else_Opcode.GetName()); writer_->Indent(); writer_->WriteNewline(FORCE_NEWLINE); } return Result::Ok; } Result WatWriter::ExprVisitorDelegate::EndIfExpr(IfExpr* expr) { writer_->WriteEndBlock(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLoadExpr(LoadExpr* expr) { writer_->WriteMemoryLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLocalGetExpr(LocalGetExpr* expr) { writer_->WritePutsSpace(Opcode::LocalGet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLocalSetExpr(LocalSetExpr* expr) { writer_->WritePutsSpace(Opcode::LocalSet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLocalTeeExpr(LocalTeeExpr* expr) { writer_->WritePutsSpace(Opcode::LocalTee_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::BeginLoopExpr(LoopExpr* expr) { writer_->WriteBeginBlock(LabelType::Loop, expr->block, Opcode::Loop_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::EndLoopExpr(LoopExpr* expr) { writer_->WriteEndBlock(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnMemoryCopyExpr(MemoryCopyExpr* expr) { writer_->WritePutsSpace(Opcode::MemoryCopy_Opcode.GetName()); writer_->WriteTwoMemoryVarsUnlessBothZero(expr->srcmemidx, expr->destmemidx, NextChar::Space); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnDataDropExpr(DataDropExpr* expr) { writer_->WritePutsSpace(Opcode::DataDrop_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnMemoryFillExpr(MemoryFillExpr* expr) { writer_->WritePutsSpace(Opcode::MemoryFill_Opcode.GetName()); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnMemoryGrowExpr(MemoryGrowExpr* expr) { writer_->WritePutsSpace(Opcode::MemoryGrow_Opcode.GetName()); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnMemorySizeExpr(MemorySizeExpr* expr) { writer_->WritePutsSpace(Opcode::MemorySize_Opcode.GetName()); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnMemoryInitExpr(MemoryInitExpr* expr) { writer_->WritePutsSpace(Opcode::MemoryInit_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Space); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableCopyExpr(TableCopyExpr* expr) { writer_->WritePutsSpace(Opcode::TableCopy_Opcode.GetName()); if (!(VarIsZero(expr->dst_table) && VarIsZero(expr->src_table))) { writer_->WriteVar(expr->dst_table, NextChar::Space); writer_->WriteVar(expr->src_table, NextChar::Space); } writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnElemDropExpr(ElemDropExpr* expr) { writer_->WritePutsSpace(Opcode::ElemDrop_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableInitExpr(TableInitExpr* expr) { writer_->WritePutsSpace(Opcode::TableInit_Opcode.GetName()); writer_->WriteVarUnlessZero(expr->table_index, NextChar::Space); writer_->WriteVar(expr->segment_index, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableGetExpr(TableGetExpr* expr) { writer_->WritePutsSpace(Opcode::TableGet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableSetExpr(TableSetExpr* expr) { writer_->WritePutsSpace(Opcode::TableSet_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableGrowExpr(TableGrowExpr* expr) { writer_->WritePutsSpace(Opcode::TableGrow_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableSizeExpr(TableSizeExpr* expr) { writer_->WritePutsSpace(Opcode::TableSize_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTableFillExpr(TableFillExpr* expr) { writer_->WritePutsSpace(Opcode::TableFill_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnRefFuncExpr(RefFuncExpr* expr) { writer_->WritePutsSpace(Opcode::RefFunc_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnRefNullExpr(RefNullExpr* expr) { writer_->WritePutsSpace(Opcode::RefNull_Opcode.GetName()); writer_->WriteRefKind(expr->type, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnRefIsNullExpr(RefIsNullExpr* expr) { writer_->WritePutsNewline(Opcode::RefIsNull_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnNopExpr(NopExpr* expr) { writer_->WritePutsNewline(Opcode::Nop_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnReturnExpr(ReturnExpr* expr) { writer_->WritePutsNewline(Opcode::Return_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnReturnCallExpr(ReturnCallExpr* expr) { writer_->WritePutsSpace(Opcode::ReturnCall_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnReturnCallIndirectExpr( ReturnCallIndirectExpr* expr) { writer_->WritePutsSpace(Opcode::ReturnCallIndirect_Opcode.GetName()); writer_->WriteOpenSpace("type"); writer_->WriteVar(expr->decl.type_var, NextChar::Space); writer_->WriteCloseNewline(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnSelectExpr(SelectExpr* expr) { writer_->WritePutsSpace(Opcode::Select_Opcode.GetName()); if (!expr->result_type.empty()) { writer_->WriteTypes(expr->result_type, "result"); } writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnStoreExpr(StoreExpr* expr) { writer_->WriteMemoryLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnUnaryExpr(UnaryExpr* expr) { writer_->WritePutsNewline(expr->opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnUnreachableExpr( UnreachableExpr* expr) { writer_->WritePutsNewline(Opcode::Unreachable_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::BeginTryExpr(TryExpr* expr) { writer_->WriteBeginBlock(LabelType::Try, expr->block, Opcode::Try_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnCatchExpr(TryExpr* expr, Catch* catch_) { writer_->Dedent(); if (catch_->IsCatchAll()) { writer_->WritePutsNewline(Opcode::CatchAll_Opcode.GetName()); } else { writer_->WritePutsSpace(Opcode::Catch_Opcode.GetName()); writer_->WriteVar(catch_->var, NextChar::Newline); } writer_->Indent(); writer_->SetTopLabelType(LabelType::Catch); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnDelegateExpr(TryExpr* expr) { writer_->Dedent(); writer_->EndBlock(); writer_->WritePutsSpace(Opcode::Delegate_Opcode.GetName()); writer_->WriteVar(expr->delegate_target, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::EndTryExpr(TryExpr* expr) { writer_->WriteEndBlock(); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnThrowExpr(ThrowExpr* expr) { writer_->WritePutsSpace(Opcode::Throw_Opcode.GetName()); writer_->WriteVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnRethrowExpr(RethrowExpr* expr) { writer_->WritePutsSpace(Opcode::Rethrow_Opcode.GetName()); writer_->WriteBrVar(expr->var, NextChar::Newline); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicWaitExpr(AtomicWaitExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicFenceExpr( AtomicFenceExpr* expr) { assert(expr->consistency_model == 0); writer_->WritePutsNewline(Opcode::AtomicFence_Opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicNotifyExpr( AtomicNotifyExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicLoadExpr(AtomicLoadExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicStoreExpr( AtomicStoreExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicRmwExpr(AtomicRmwExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnAtomicRmwCmpxchgExpr( AtomicRmwCmpxchgExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnTernaryExpr(TernaryExpr* expr) { writer_->WritePutsNewline(expr->opcode.GetName()); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnSimdLaneOpExpr(SimdLaneOpExpr* expr) { writer_->WritePutsSpace(expr->opcode.GetName()); writer_->Writef("%" PRIu64, (expr->val)); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnSimdLoadLaneExpr( SimdLoadLaneExpr* expr) { writer_->WritePutsSpace(expr->opcode.GetName()); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); if (expr->offset) { writer_->Writef("offset=%" PRIaddress, expr->offset); } if (!expr->opcode.IsNaturallyAligned(expr->align)) { writer_->Writef("align=%" PRIaddress, expr->align); } writer_->Writef("%" PRIu64, (expr->val)); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnSimdStoreLaneExpr( SimdStoreLaneExpr* expr) { writer_->WritePutsSpace(expr->opcode.GetName()); writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space); if (expr->offset) { writer_->Writef("offset=%" PRIaddress, expr->offset); } if (!expr->opcode.IsNaturallyAligned(expr->align)) { writer_->Writef("align=%" PRIaddress, expr->align); } writer_->Writef("%" PRIu64, (expr->val)); writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnSimdShuffleOpExpr( SimdShuffleOpExpr* expr) { writer_->WritePutsSpace(expr->opcode.GetName()); std::array values = Bitcast>(expr->val); for (int32_t lane = 0; lane < 16; ++lane) { #if WABT_BIG_ENDIAN writer_->Writef("%u", values[15 - lane]); #else writer_->Writef("%u", values[lane]); #endif } writer_->WriteNewline(NO_FORCE_NEWLINE); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLoadSplatExpr(LoadSplatExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } Result WatWriter::ExprVisitorDelegate::OnLoadZeroExpr(LoadZeroExpr* expr) { writer_->WriteLoadStoreExpr(expr); return Result::Ok; } void WatWriter::WriteExpr(const Expr* expr) { WABT_TRACE(WriteExprList); ExprVisitorDelegate delegate(this); ExprVisitor visitor(&delegate); visitor.VisitExpr(const_cast(expr)); } void WatWriter::WriteExprList(const ExprList& exprs) { WABT_TRACE(WriteExprList); ExprVisitorDelegate delegate(this); ExprVisitor visitor(&delegate); visitor.VisitExprList(const_cast(exprs)); } void WatWriter::WriteFoldedExpr(const Expr* expr) { WABT_TRACE_ARGS(WriteFoldedExpr, "%s", GetExprTypeName(*expr)); auto arity = GetExprArity(*expr); PushExpr(expr, arity.nargs, arity.nreturns); } void WatWriter::WriteFoldedExprList(const ExprList& exprs) { WABT_TRACE(WriteFoldedExprList); for (const Expr& expr : exprs) { WriteFoldedExpr(&expr); } } void WatWriter::PushExpr(const Expr* expr, Index operand_count, Index result_count) { WABT_TRACE_ARGS(PushExpr, "%s, %" PRIindex ", %" PRIindex "", GetExprTypeName(*expr), operand_count, result_count); // Try to pop operand off the expr stack to use as this expr's children. One // expr can have multiple return values (with the multi-value extension), so // we have to iterate over each in reverse. auto first_operand = expr_tree_stack_.end(); Index current_count = 0; if (operand_count > 0) { for (auto iter = expr_tree_stack_.rbegin(); iter != expr_tree_stack_.rend(); ++iter) { assert(iter->result_count > 0); current_count += iter->result_count; if (current_count == operand_count) { first_operand = iter.base() - 1; break; } else if (current_count > operand_count) { // We went over the number of operands this instruction wants; this can // only happen when there are expressions on the stack with a // result_count > 1. When this happens we can't fold, since any result // we produce will not make sense. break; } } } ExprTree tree(expr, result_count); if (current_count == operand_count && operand_count > 0) { auto last_operand = expr_tree_stack_.end(); std::move(first_operand, last_operand, std::back_inserter(tree.children)); expr_tree_stack_.erase(first_operand, last_operand); } expr_tree_stack_.emplace_back(std::move(tree)); if (current_count > operand_count || result_count == 0) { FlushExprTreeStack(); } } void WatWriter::FlushExprTree(const ExprTree& expr_tree) { WABT_TRACE_ARGS(FlushExprTree, "%s", GetExprTypeName(*expr_tree.expr)); switch (expr_tree.expr->type()) { case ExprType::Block: WritePuts("(", NextChar::None); WriteBeginBlock(LabelType::Block, cast(expr_tree.expr)->block, Opcode::Block_Opcode.GetName()); WriteFoldedExprList(cast(expr_tree.expr)->block.exprs); FlushExprTreeStack(); WriteCloseNewline(); EndBlock(); break; case ExprType::Loop: WritePuts("(", NextChar::None); WriteBeginBlock(LabelType::Loop, cast(expr_tree.expr)->block, Opcode::Loop_Opcode.GetName()); WriteFoldedExprList(cast(expr_tree.expr)->block.exprs); FlushExprTreeStack(); WriteCloseNewline(); EndBlock(); break; case ExprType::If: { auto if_expr = cast(expr_tree.expr); WritePuts("(", NextChar::None); WriteBeginBlock(LabelType::If, if_expr->true_, Opcode::If_Opcode.GetName()); FlushExprTreeVector(expr_tree.children); WriteOpenNewline("then"); WriteFoldedExprList(if_expr->true_.exprs); FlushExprTreeStack(); WriteCloseNewline(); if (!if_expr->false_.empty()) { WriteOpenNewline("else"); WriteFoldedExprList(if_expr->false_); FlushExprTreeStack(); WriteCloseNewline(); } WriteCloseNewline(); EndBlock(); break; } case ExprType::Try: { auto try_expr = cast(expr_tree.expr); WritePuts("(", NextChar::None); WriteBeginBlock(LabelType::Try, try_expr->block, Opcode::Try_Opcode.GetName()); WriteOpenNewline("do"); FlushExprTreeVector(expr_tree.children); WriteFoldedExprList(try_expr->block.exprs); FlushExprTreeStack(); WriteCloseNewline(); switch (try_expr->kind) { case TryKind::Catch: for (const Catch& catch_ : try_expr->catches) { WritePuts("(", NextChar::None); if (catch_.IsCatchAll()) { WritePutsNewline("catch_all"); } else { WritePutsSpace(Opcode::Catch_Opcode.GetName()); WriteVar(catch_.var, NextChar::Newline); } Indent(); WriteFoldedExprList(catch_.exprs); FlushExprTreeStack(); WriteCloseNewline(); } break; case TryKind::Delegate: WritePuts("(", NextChar::None); WritePutsSpace(Opcode::Delegate_Opcode.GetName()); WriteVar(try_expr->delegate_target, NextChar::None); WritePuts(")", NextChar::Newline); break; case TryKind::Plain: // Nothing to do. break; } WriteCloseNewline(); EndBlock(); break; } default: { WritePuts("(", NextChar::None); WriteExpr(expr_tree.expr); Indent(); FlushExprTreeVector(expr_tree.children); WriteCloseNewline(); break; } } } void WatWriter::FlushExprTreeVector(const std::vector& expr_trees) { WABT_TRACE_ARGS(FlushExprTreeVector, "%zu", expr_trees.size()); for (auto expr_tree : expr_trees) { FlushExprTree(expr_tree); } } void WatWriter::FlushExprTreeStack() { std::vector stack_copy(std::move(expr_tree_stack_)); expr_tree_stack_.clear(); FlushExprTreeVector(stack_copy); } void WatWriter::WriteInitExpr(const ExprList& expr) { if (!expr.empty()) { WritePuts("(", NextChar::None); WriteExprList(expr); /* clear the next char, so we don't write a newline after the expr */ next_char_ = NextChar::None; WritePuts(")", NextChar::Space); } } template void WatWriter::WriteTypeBindings(const char* prefix, const T& types, const std::vector& index_to_name, Index binding_index_offset) { /* named params/locals must be specified by themselves, but nameless * params/locals can be compressed, e.g.: * (param $foo i32) * (param i32 i64 f32) */ bool first = true; bool prev_has_name = false; size_t index = 0; for (Type type : types) { const std::string& name = index_to_name[binding_index_offset + index]; bool has_name = !name.empty(); if ((has_name || prev_has_name) && !first) { WriteCloseSpace(); } if (has_name || prev_has_name || first) { WriteOpenSpace(prefix); } if (has_name) { WriteString(name, NextChar::Space); } WriteType(type, NextChar::Space); prev_has_name = has_name; first = false; ++index; } if (types.size() != 0) { WriteCloseSpace(); } } void WatWriter::WriteBeginFunc(const Func& func) { WriteOpenSpace("func"); WriteNameOrIndex(func.name, func_index_, NextChar::Space); WriteInlineExports(ExternalKind::Func, func_index_); WriteInlineImport(ExternalKind::Func, func_index_); if (func.decl.has_func_type) { WriteOpenSpace("type"); WriteVar(func.decl.type_var, NextChar::None); WriteCloseSpace(); } if (module.IsImport(ExternalKind::Func, Var(func_index_, Location()))) { // Imported functions can be written a few ways: // // 1. (import "module" "field" (func (type 0))) // 2. (import "module" "field" (func (param i32) (result i32))) // 3. (func (import "module" "field") (type 0)) // 4. (func (import "module" "field") (param i32) (result i32)) // 5. (func (import "module" "field") (type 0) (param i32) (result i32)) // // Note that the text format does not allow including the param/result // explicitly when using the "(import..." syntax (#1 and #2). if (options_.inline_import || !func.decl.has_func_type) { WriteFuncSigSpace(func.decl.sig); } } func_index_++; } void WatWriter::WriteFunc(const Func& func) { WriteBeginFunc(func); std::vector index_to_name; MakeTypeBindingReverseMapping(func.GetNumParamsAndLocals(), func.bindings, &index_to_name); WriteTypeBindings("param", func.decl.sig.param_types, index_to_name); WriteTypes(func.decl.sig.result_types, "result"); WriteNewline(NO_FORCE_NEWLINE); if (func.local_types.size()) { WriteTypeBindings("local", func.local_types, index_to_name, func.GetNumParams()); } WriteNewline(NO_FORCE_NEWLINE); BeginFunc(func); if (options_.fold_exprs) { WriteFoldedExprList(func.exprs); FlushExprTreeStack(); } else { WriteExprList(func.exprs); } EndFunc(); WriteCloseNewline(); } void WatWriter::WriteBeginGlobal(const Global& global) { WriteOpenSpace("global"); WriteNameOrIndex(global.name, global_index_, NextChar::Space); WriteInlineExports(ExternalKind::Global, global_index_); WriteInlineImport(ExternalKind::Global, global_index_); if (global.mutable_) { WriteOpenSpace("mut"); WriteType(global.type, NextChar::Space); WriteCloseSpace(); } else { WriteType(global.type, NextChar::Space); } global_index_++; } void WatWriter::WriteGlobal(const Global& global) { WriteBeginGlobal(global); WriteInitExpr(global.init_expr); WriteCloseNewline(); } void WatWriter::WriteTag(const Tag& tag) { WriteOpenSpace("tag"); WriteNameOrIndex(tag.name, tag_index_, NextChar::Space); WriteInlineExports(ExternalKind::Tag, tag_index_); WriteInlineImport(ExternalKind::Tag, tag_index_); if (tag.decl.has_func_type) { WriteOpenSpace("type"); WriteVar(tag.decl.type_var, NextChar::None); WriteCloseSpace(); } WriteTypes(tag.decl.sig.param_types, "param"); ++tag_index_; WriteCloseNewline(); } void WatWriter::WriteLimits(const Limits& limits) { if (limits.is_64) { Writef("i64"); } Writef("%" PRIu64, limits.initial); if (limits.has_max) { Writef("%" PRIu64, limits.max); } if (limits.is_shared) { Writef("shared"); } } void WatWriter::WriteTable(const Table& table) { WriteOpenSpace("table"); WriteNameOrIndex(table.name, table_index_, NextChar::Space); WriteInlineExports(ExternalKind::Table, table_index_); WriteInlineImport(ExternalKind::Table, table_index_); WriteLimits(table.elem_limits); WriteType(table.elem_type, NextChar::None); WriteCloseNewline(); table_index_++; } void WatWriter::WriteElemSegment(const ElemSegment& segment) { WriteOpenSpace("elem"); // The first name we encounter here, pre-bulk-memory, was intended to refer to // the table while segment names were not supported at all. For this reason // we cannot emit a segment name here without bulk-memory enabled, otherwise // the name will be assumed to be the name of a table and parsing will fail. if (options_.features.bulk_memory_enabled()) { WriteNameOrIndex(segment.name, elem_segment_index_, NextChar::Space); } else { Writef("(;%u;)", elem_segment_index_); } uint8_t flags = segment.GetFlags(&module); if ((flags & (SegPassive | SegExplicitIndex)) == SegExplicitIndex) { WriteOpenSpace("table"); WriteVar(segment.table_var, NextChar::Space); WriteCloseSpace(); } if (!(flags & SegPassive)) { WriteInitExpr(segment.offset); } if ((flags & SegDeclared) == SegDeclared) { WritePuts("declare", NextChar::Space); } if (flags & SegUseElemExprs) { WriteType(segment.elem_type, NextChar::Space); } else { assert(segment.elem_type == Type::FuncRef); WritePuts("func", NextChar::Space); } for (const ExprList& expr : segment.elem_exprs) { if (flags & SegUseElemExprs) { WriteInitExpr(expr); } else { assert(expr.size() == 1); assert(expr.front().type() == ExprType::RefFunc); WriteVar(cast(&expr.front())->var, NextChar::Space); } } WriteCloseNewline(); elem_segment_index_++; } void WatWriter::WriteMemory(const Memory& memory) { WriteOpenSpace("memory"); WriteNameOrIndex(memory.name, memory_index_, NextChar::Space); WriteInlineExports(ExternalKind::Memory, memory_index_); WriteInlineImport(ExternalKind::Memory, memory_index_); WriteLimits(memory.page_limits); WriteCloseNewline(); memory_index_++; } void WatWriter::WriteDataSegment(const DataSegment& segment) { WriteOpenSpace("data"); WriteNameOrIndex(segment.name, data_segment_index_, NextChar::Space); if (segment.kind != SegmentKind::Passive) { WriteMemoryVarUnlessZero(segment.memory_var, NextChar::Space); WriteInitExpr(segment.offset); } WriteQuotedData(segment.data.data(), segment.data.size()); WriteCloseNewline(); data_segment_index_++; } void WatWriter::WriteImport(const Import& import) { if (!options_.inline_import) { WriteOpenSpace("import"); WriteQuotedString(import.module_name, NextChar::Space); WriteQuotedString(import.field_name, NextChar::Space); } switch (import.kind()) { case ExternalKind::Func: WriteBeginFunc(cast(&import)->func); WriteCloseSpace(); break; case ExternalKind::Table: WriteTable(cast(&import)->table); break; case ExternalKind::Memory: WriteMemory(cast(&import)->memory); break; case ExternalKind::Global: WriteBeginGlobal(cast(&import)->global); WriteCloseSpace(); break; case ExternalKind::Tag: WriteTag(cast(&import)->tag); break; } if (options_.inline_import) { WriteNewline(NO_FORCE_NEWLINE); } else { WriteCloseNewline(); } } void WatWriter::WriteExport(const Export& export_) { if (options_.inline_export && IsInlineExport(export_)) { return; } WriteOpenSpace("export"); WriteQuotedString(export_.name, NextChar::Space); WriteOpenSpace(GetKindName(export_.kind)); WriteVar(export_.var, NextChar::Space); WriteCloseSpace(); WriteCloseNewline(); } void WatWriter::WriteTypeEntry(const TypeEntry& type) { WriteOpenSpace("type"); WriteNameOrIndex(type.name, type_index_++, NextChar::Space); switch (type.kind()) { case TypeEntryKind::Func: WriteOpenSpace("func"); WriteFuncSigSpace(cast(&type)->sig); WriteCloseSpace(); break; case TypeEntryKind::Struct: { auto* struct_type = cast(&type); WriteOpenSpace("struct"); Index field_index = 0; for (auto&& field : struct_type->fields) { // TODO: Write shorthand if there is no name. WriteOpenSpace("field"); WriteNameOrIndex(field.name, field_index++, NextChar::Space); WriteField(field); WriteCloseSpace(); } WriteCloseSpace(); break; } case TypeEntryKind::Array: { auto* array_type = cast(&type); WriteOpenSpace("array"); WriteField(array_type->field); WriteCloseSpace(); break; } } WriteCloseNewline(); } void WatWriter::WriteField(const Field& field) { if (field.mutable_) { WriteOpenSpace("mut"); } WriteType(field.type, NextChar::Space); if (field.mutable_) { WriteCloseSpace(); } } void WatWriter::WriteStartFunction(const Var& start) { WriteOpenSpace("start"); WriteVar(start, NextChar::None); WriteCloseNewline(); } Result WatWriter::WriteModule() { BuildInlineExportMap(); BuildInlineImportMap(); WriteOpenSpace("module"); if (module.name.empty()) { WriteNewline(NO_FORCE_NEWLINE); } else { WriteName(module.name, NextChar::Newline); } for (const ModuleField& field : module.fields) { switch (field.type()) { case ModuleFieldType::Func: WriteFunc(cast(&field)->func); break; case ModuleFieldType::Global: WriteGlobal(cast(&field)->global); break; case ModuleFieldType::Import: WriteImport(*cast(&field)->import); break; case ModuleFieldType::Tag: WriteTag(cast(&field)->tag); break; case ModuleFieldType::Export: WriteExport(cast(&field)->export_); break; case ModuleFieldType::Table: WriteTable(cast(&field)->table); break; case ModuleFieldType::ElemSegment: WriteElemSegment(cast(&field)->elem_segment); break; case ModuleFieldType::Memory: WriteMemory(cast(&field)->memory); break; case ModuleFieldType::DataSegment: WriteDataSegment(cast(&field)->data_segment); break; case ModuleFieldType::Type: WriteTypeEntry(*cast(&field)->type); break; case ModuleFieldType::Start: WriteStartFunction(cast(&field)->start); break; } } WriteCloseNewline(); /* force the newline to be written */ WriteNextChar(); return result_; } void WatWriter::BuildInlineExportMap() { if (!options_.inline_export) { return; } for (Export* export_ : module.exports) { Index index = kInvalidIndex; // Exported imports can't be written with inline exports, unless the // imports are also inline. For example, the following is invalid: // // (import "module" "field" (func (export "e"))) // // But this is valid: // // (func (export "e") (import "module" "field")) // if (!options_.inline_import && module.IsImport(*export_)) { continue; } switch (export_->kind) { case ExternalKind::Func: index = module.GetFuncIndex(export_->var); break; case ExternalKind::Table: index = module.GetTableIndex(export_->var); break; case ExternalKind::Memory: index = module.GetMemoryIndex(export_->var); break; case ExternalKind::Global: index = module.GetGlobalIndex(export_->var); break; case ExternalKind::Tag: index = module.GetTagIndex(export_->var); break; } if (index != kInvalidIndex) { auto key = std::make_pair(export_->kind, index); inline_export_map_.insert(std::make_pair(key, export_)); } } } void WatWriter::WriteInlineExports(ExternalKind kind, Index index) { if (!options_.inline_export) { return; } auto iter_pair = inline_export_map_.equal_range(std::make_pair(kind, index)); for (auto iter = iter_pair.first; iter != iter_pair.second; ++iter) { const Export* export_ = iter->second; WriteOpenSpace("export"); WriteQuotedString(export_->name, NextChar::None); WriteCloseSpace(); } } bool WatWriter::IsInlineExport(const Export& export_) { Index index{}; switch (export_.kind) { case ExternalKind::Func: index = module.GetFuncIndex(export_.var); break; case ExternalKind::Table: index = module.GetTableIndex(export_.var); break; case ExternalKind::Memory: index = module.GetMemoryIndex(export_.var); break; case ExternalKind::Global: index = module.GetGlobalIndex(export_.var); break; case ExternalKind::Tag: index = module.GetTagIndex(export_.var); break; } return inline_export_map_.find(std::make_pair(export_.kind, index)) != inline_export_map_.end(); } void WatWriter::BuildInlineImportMap() { if (!options_.inline_import) { return; } for (const Import* import : module.imports) { inline_import_map_[static_cast(import->kind())].push_back(import); } } void WatWriter::WriteInlineImport(ExternalKind kind, Index index) { if (!options_.inline_import) { return; } size_t kind_index = static_cast(kind); if (index >= inline_import_map_[kind_index].size()) { return; } const Import* import = inline_import_map_[kind_index][index]; WriteOpenSpace("import"); WriteQuotedString(import->module_name, NextChar::Space); WriteQuotedString(import->field_name, NextChar::Space); WriteCloseSpace(); } } // end anonymous namespace Result WriteWat(Stream* stream, const Module* module, const WriteWatOptions& options) { WatWriter wat_writer(stream, options, *module); return wat_writer.WriteModule(); } } // namespace wabt