From 127c7b8e1e60f4a081c2173b9826230aac46b761 Mon Sep 17 00:00:00 2001 From: Ben Smith Date: Tue, 3 Nov 2020 13:23:36 -0800 Subject: [PATCH] Implement ToText, for converting binary -> text (#21) --- include/wasp/convert/to_text.h | 133 +++++ src/convert/CMakeLists.txt | 2 + src/convert/to_text.cc | 740 +++++++++++++++++++++++ test/convert/CMakeLists.txt | 1 + test/convert/to_text_test.cc | 1020 ++++++++++++++++++++++++++++++++ 5 files changed, 1896 insertions(+) create mode 100644 include/wasp/convert/to_text.h create mode 100644 src/convert/to_text.cc create mode 100644 test/convert/to_text_test.cc diff --git a/include/wasp/convert/to_text.h b/include/wasp/convert/to_text.h new file mode 100644 index 00000000..7816e90c --- /dev/null +++ b/include/wasp/convert/to_text.h @@ -0,0 +1,133 @@ +// +// Copyright 2020 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. +// + +#ifndef WASP_CONVERT_TO_TEXT_H_ +#define WASP_CONVERT_TO_TEXT_H_ + +#include +#include +#include +#include + +#include "wasp/base/at.h" +#include "wasp/base/buffer.h" +#include "wasp/base/optional.h" +#include "wasp/binary/types.h" +#include "wasp/text/types.h" + +namespace wasp::convert { + +// TODO: Rename +struct TextContext { + text::Text Add(string_view); + + // TODO: The buffers need to have stable pointers (for use by string_view or + // span), so for now they are all std::unique_ptr. This could be optimized + // in a number of ways, however (e.g. allocating in larger blocks of chars, + // and appending to that). + std::vector> strings; +}; + +// Helpers. +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const binary::ValueTypeList&) -> text::ValueTypeList; +auto ToTextBound(TextContext&, const binary::ValueTypeList&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const OptAt&) -> OptAt; +auto ToText(TextContext&, const binary::IndexList&) -> text::VarList; +auto ToText(TextContext&, const At&) -> At; + +// Section 1: Type +auto ToTextBound(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const binary::FieldTypeList&) -> text::FieldTypeList; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; + +// Section 2: Import +auto ToText(TextContext&, const At&) -> At; + +// Section 3: Function +auto ToText(TextContext&, const At&) -> At; + +// Section 4: Table +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; + +// Section 5: Memory +auto ToText(TextContext&, const At&) -> At; + +// Section 6: Global +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; + +// Section 7: Export +auto ToText(TextContext&, const At&) -> At; + +// Section 8: Start +auto ToText(TextContext&, const At&) -> At; + +// Section 9: Elem +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const binary::ElementExpressionList&) -> text::ElementExpressionList; +auto ToText(TextContext&, const binary::ElementList&) -> text::ElementList; +auto ToText(TextContext&, const At&) -> At; + +// Section 10: Code +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const binary::InstructionList&) -> text::InstructionList; + +auto ToText(TextContext&, const At&) -> text::InstructionList; +auto ToText(TextContext&, const binary::LocalsList&) -> At; +auto ToText(TextContext&, const At&, At&) -> At&; + +// Section 11: Data +auto ToText(TextContext&, const At&) -> text::TextList; +auto ToText(TextContext&, const At&) -> At; + +// Section 12: DataCount + +// Section 13: Event +auto ToText(TextContext&, const At&) -> At; +auto ToText(TextContext&, const At&) -> At; + +// Module +auto ToText(TextContext&, const At&) -> At; + +} // namespace wasp::convert + +#endif // WASP_CONVERT_TO_TEXT_H_ diff --git a/src/convert/CMakeLists.txt b/src/convert/CMakeLists.txt index 45e1732f..ef4a54e3 100644 --- a/src/convert/CMakeLists.txt +++ b/src/convert/CMakeLists.txt @@ -16,8 +16,10 @@ add_library(libwasp_convert ../../include/wasp/convert/to_binary.h + ../../include/wasp/convert/to_text.h to_binary.cc + to_text.cc ) target_compile_options(libwasp_convert diff --git a/src/convert/to_text.cc b/src/convert/to_text.cc new file mode 100644 index 00000000..f497bf5b --- /dev/null +++ b/src/convert/to_text.cc @@ -0,0 +1,740 @@ +// +// Copyright 2020 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 "wasp/convert/to_text.h" + +#include + +#include "wasp/base/enumerate.h" +#include "wasp/base/macros.h" + +namespace wasp::convert { + +std::string EncodeAsText(string_view str) { + const char kHexDigit[] = "0123456789abcdef"; + std::string text = "\""; + for (auto byte : str) { + if (byte == '"') { + text += "\\\""; + } else if (byte == '\\') { + text += "\\\\"; + } else if (byte >= 32 && byte <= 127) { + text += byte; + } else if (byte == '\t') { + text += "\\t"; + } else if (byte == '\n') { + text += "\\n"; + } else if (byte == '\r') { + text += "\\r"; + } else { + text += "\\"; + text += kHexDigit[byte >> 4]; + text += kHexDigit[byte & 15]; + } + } + text += "\""; + return text; +} + +text::Text TextContext::Add(string_view str) { + strings.push_back(std::make_unique(EncodeAsText(str))); + return text::Text{string_view{*strings.back()}, static_cast(str.size())}; +} + +// Helpers. +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_heap_kind()) { + return At{value.loc(), text::HeapType{value->heap_kind()}}; + } else { + assert(value->is_index()); + return At{value.loc(), text::HeapType{ToText(context, value->index())}}; + } +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::RefType{ToText(context, value->heap_type), value->null}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_reference_kind()) { + return At{value.loc(), text::ReferenceType{value->reference_kind()}}; + } else { + assert(value->is_ref()); + return At{value.loc(), text::ReferenceType{ToText(context, value->ref())}}; + } +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::Rtt{value->depth, ToText(context, value->type)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_numeric_type()) { + return At{value.loc(), text::ValueType{value->numeric_type()}}; + } else if (value->is_reference_type()) { + return At{value.loc(), + text::ValueType{ToText(context, value->reference_type())}}; + } else { + assert(value->is_rtt()); + return At{value.loc(), text::ValueType{ToText(context, value->rtt())}}; + } +} + +auto ToText(TextContext& context, const binary::ValueTypeList& values) + -> text::ValueTypeList { + text::ValueTypeList result; + for (auto&& value : values) { + result.push_back(ToText(context, value)); + } + return result; +} + +auto ToTextBound(TextContext& context, const binary::ValueTypeList& values) + -> At { + text::BoundValueTypeList result; + for (auto&& value : values) { + result.push_back(text::BoundValueType{nullopt, // TODO name + ToText(context, value)}); + } + return result; +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_value_type()) { + return At{value.loc(), + text::StorageType{ToText(context, value->value_type())}}; + } else { + assert(value->is_packed_type()); + return At{value.loc(), text::StorageType{value->packed_type()}}; + } +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), context.Add(*value)}; +} + +auto ToText(TextContext& context, const At& value) -> At { + return At{value.loc(), text::Var{value.value()}}; +} + +auto ToText(TextContext& context, const OptAt& value) + -> OptAt { + if (!value) { + return nullopt; + } + return ToText(context, *value); +} + +auto ToText(TextContext& context, const binary::IndexList& values) + -> text::VarList { + text::VarList result; + for (auto value : values) { + result.push_back(ToText(context, value)); + } + return result; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::FunctionType{ToText(context, value->param_types), + ToText(context, value->result_types)}}; +} + +// Section 1: Type +auto ToTextBound(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::BoundFunctionType{ToTextBound(context, value->param_types), + ToText(context, value->result_types)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::FieldType{nullopt, // TODO(name) + ToText(context, value->type), value->mut}}; +} + +auto ToText(TextContext& context, const binary::FieldTypeList& values) + -> text::FieldTypeList { + text::FieldTypeList result; + for (auto&& value : values) { + result.push_back(ToText(context, value)); + } + return result; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::StructType{ToText(context, value->fields)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::ArrayType{ToText(context, value->field)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_function_type()) { + return At{value.loc(), + text::DefinedType{nullopt, // TODO(name) + ToTextBound(context, value->function_type())}}; + } else if (value->is_struct_type()) { + return At{value.loc(), + text::DefinedType{nullopt, // TODO(name) + ToText(context, value->struct_type())}}; + } else { + assert(value->is_array_type()); + return At{value.loc(), + text::DefinedType{nullopt, // TODO(name) + ToText(context, value->array_type())}}; + } +} + +// Section 2: Import +auto ToText(TextContext& context, const At& value) + -> At { + auto module = ToText(context, value->module); + auto name = ToText(context, value->name); + + switch (value->kind()) { + case ExternalKind::Function: + return At{value.loc(), + text::Import{module, name, + text::FunctionDesc{ + nullopt, // TODO(name) + ToText(context, value->index()), + {} // Unresolved bound function type + }}}; + + case ExternalKind::Table: + return At{ + value.loc(), + text::Import{module, name, + text::TableDesc{nullopt, // TODO(name) + ToText(context, value->table_type())}}}; + + case ExternalKind::Memory: + return At{value.loc(), + text::Import{module, name, + text::MemoryDesc{nullopt, // TODO(name) + value->memory_type()}}}; + + case ExternalKind::Global: + return At{value.loc(), + text::Import{ + module, name, + text::GlobalDesc{nullopt, // TODO(name) + ToText(context, value->global_type())}}}; + + case ExternalKind::Event: + return At{ + value.loc(), + text::Import{module, name, + text::EventDesc{nullopt, // TODO(name) + ToText(context, value->event_type())}}}; + + default: + WASP_UNREACHABLE(); + } +} + +// Section 3: Function +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::Function{text::FunctionDesc{ + nullopt, // TODO(name) + ToText(context, value->type_index), + {} // Unresolved bound function type + }, + {}, + {}, + nullopt, + {}}}; +} + +// Section 4: Table +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::TableType{value->limits, ToText(context, value->elemtype)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::Table{text::TableDesc{nullopt, // TODO(name) + ToText(context, value->table_type)}, + {}}}; +} + +// Section 5: Memory +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::Memory{text::MemoryDesc{nullopt, // TODO(name) + value->memory_type}, + {}}}; +} + +// Section 6: Global +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::ConstantExpression{ToText(context, value->instructions)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::GlobalType{ToText(context, value->valtype), value->mut}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::Global{text::GlobalDesc{nullopt, // TODO(name) + ToText(context, value->global_type)}, + ToText(context, value->init), + {}}}; +} + +// Section 7: Export +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::Export{value->kind, ToText(context, value->name), + ToText(context, value->index)}}; +} + +// Section 8: Start +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::Start{ToText(context, value->func_index)}}; +} + +// Section 9: Elem +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::ElementExpression{ToText(context, value->instructions)}}; +} + +auto ToText(TextContext& context, const binary::ElementExpressionList& values) + -> text::ElementExpressionList { + text::ElementExpressionList result; + for (auto&& value : values) { + result.push_back(ToText(context, value)); + } + return result; +} + +auto ToText(TextContext& context, const binary::ElementList& value) + -> text::ElementList { + if (holds_alternative(value)) { + auto&& elements = get(value); + return text::ElementListWithExpressions{ToText(context, elements.elemtype), + ToText(context, elements.list)}; + } else { + auto&& elements = get(value); + return text::ElementListWithVars{elements.kind, + ToText(context, elements.list)}; + } +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->type == SegmentType::Active) { + return At{value.loc(), + text::ElementSegment{nullopt, // TODO(name) + ToText(context, value->table_index), + ToText(context, *value->offset), + ToText(context, value->elements)}}; + } else { + return At{value.loc(), text::ElementSegment{ + nullopt, // TODO(name) + value->type, ToText(context, value->elements)}}; + } +} + +// Section 10: Code +auto ToText(TextContext& context, const At& value) + -> At { + if (value->is_value_type()) { + return At{ + value.loc(), + text::BlockImmediate{ + nullopt, // TODO(name) + text::FunctionTypeUse{ + nullopt, text::FunctionType{ + {}, {ToText(context, value->value_type())}}}}}; + } else if (value->is_void()) { + return At{value.loc(), + text::BlockImmediate{ + nullopt, // TODO(name) + text::FunctionTypeUse{nullopt, text::FunctionType{{}, {}}}}}; + } else { + assert(value->is_index()); + return At{value.loc(), + text::BlockImmediate{ + nullopt, // TODO(name) + text::FunctionTypeUse{ToText(context, value->index()), {}}}}; + } +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::BrOnCastImmediate{ToText(context, value->target), + ToText(context, value->types)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::BrOnExnImmediate{ToText(context, value->target), + ToText(context, value->event_index)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::BrTableImmediate{ToText(context, value->targets), + ToText(context, value->default_target)}}; +} + +auto ToText(TextContext& context, + const At& value) + -> At { + return At{value.loc(), + text::CallIndirectImmediate{ + ToText(context, value->table_index), + text::FunctionTypeUse{ToText(context, value->index), {}}}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::CopyImmediate{ToText(context, value->dst_index), + ToText(context, value->src_index)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::FuncBindImmediate{ToText(context, value->index), {}}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::HeapType2Immediate{ToText(context, value->parent), + ToText(context, value->child)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::InitImmediate{ToText(context, value->segment_index), + ToText(context, value->dst_index)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::LetImmediate{ToText(context, value->block_type), + ToText(context, value->locals)}}; +} + +auto GetAlignPow2(At align_log2) -> At { + // Must be less than 32. + assert(*align_log2 < 32); + return At{align_log2.loc(), 1u << align_log2}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), text::MemArgImmediate{GetAlignPow2(value->align_log2), + value->offset}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::RttSubImmediate{value->depth, ToText(context, value->types)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::StructFieldImmediate{ToText(context, value->struct_), + ToText(context, value->field)}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + switch (value->immediate.index()) { + case 0: // monostate + return At{value.loc(), text::Instruction{value->opcode}}; + + case 1: // s32 + return At{value.loc(), + text::Instruction{value->opcode, value->s32_immediate()}}; + + case 2: // s64 + return At{value.loc(), + text::Instruction{value->opcode, value->s64_immediate()}}; + + case 3: // f32 + return At{value.loc(), + text::Instruction{value->opcode, value->f32_immediate()}}; + + case 4: // f64 + return At{value.loc(), + text::Instruction{value->opcode, value->f64_immediate()}}; + + case 5: // v128 + return At{value.loc(), + text::Instruction{value->opcode, value->v128_immediate()}}; + + case 6: // Index + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->index_immediate())}}; + + case 7: // BlockType + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->block_type_immediate())}}; + + case 8: // BrOnExnImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->br_on_exn_immediate())}}; + + case 9: // BrTableImmediate + return At{value.loc(), text::Instruction{ + value->opcode, + ToText(context, value->br_table_immediate())}}; + + case 10: // CallIndirectImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->call_indirect_immediate())}}; + + case 11: // CopyImmediate + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->copy_immediate())}}; + + case 12: // InitImmediate + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->init_immediate())}}; + + case 13: // LetImmediate + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->let_immediate())}}; + + case 14: // MemArgImmediate + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->mem_arg_immediate())}}; + + case 15: // HeapType + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->heap_type_immediate())}}; + + case 16: // SelectImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + At{value->select_immediate().loc(), + ToText(context, *value->select_immediate())}}}; + + case 17: // ShuffleImmediate + return At{value.loc(), + text::Instruction{value->opcode, value->shuffle_immediate()}}; + + case 18: // SimdLaneImmediate + return At{value.loc(), + text::Instruction{value->opcode, value->simd_lane_immediate()}}; + + case 19: // FuncBindImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->func_bind_immediate())}}; + + case 20: // BrOnCastImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->br_on_cast_immediate())}}; + + case 21: // HeapType2Immediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->heap_type_2_immediate())}}; + + case 22: // RttSubImmediate + return At{value.loc(), + text::Instruction{value->opcode, + ToText(context, value->rtt_sub_immediate())}}; + + case 23: // StructFieldImmediate + return At{ + value.loc(), + text::Instruction{value->opcode, + ToText(context, value->struct_field_immediate())}}; + + default: + WASP_UNREACHABLE(); + } +} + +auto ToText(TextContext& context, const binary::InstructionList& values) + -> text::InstructionList { + text::InstructionList result; + for (auto&& value : values) { + result.push_back(ToText(context, value)); + } + return result; +} + +auto ToText(TextContext& context, const At& value) + -> text::InstructionList { + return ToText(context, value->instructions); +} + +auto ToText(TextContext& context, const binary::LocalsList& values) + -> At { + text::BoundValueTypeList result; + for (auto&& locals : values) { + auto type = ToText(context, locals->type); + for (Index i = 0; i < *locals->count; ++i) { + result.push_back(text::BoundValueType{nullopt, // TODO(name) + type}); + } + } + return result; +} + +auto ToText(TextContext& context, + const At& value, + At& function) -> At& { + function->locals = ToText(context, value->locals); + function->instructions = ToText(context, value->body); + return function; +} + +// Section 11: Data +auto ToText(TextContext& context, const At& value) -> text::TextList { + return text::TextList{context.Add(ToStringView(value))}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + if (value->type == SegmentType::Active) { + return At{value.loc(), + text::DataSegment{nullopt, // TODO(name) + ToText(context, value->memory_index), + ToText(context, *value->offset), + ToText(context, value->init)}}; + } else { + return At{value.loc(), + text::DataSegment{nullopt, // TODO(name) + ToText(context, value->init)}}; + } +} + +// Section 12: DataCount + +// Section 13: Event +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::EventType{ + value->attribute, + text::FunctionTypeUse{ToText(context, value->type_index), {}}}}; +} + +auto ToText(TextContext& context, const At& value) + -> At { + return At{value.loc(), + text::Event{text::EventDesc{nullopt, // TODO(name) + ToText(context, value->event_type)}, + nullopt, + {}}}; +} + +// Module +auto ToText(TextContext& context, const At& value) + -> At { + text::Module module; + + auto do_vector = [&](const auto& items) { + for (auto&& item : items) { + module.push_back(At{item.loc(), text::ModuleItem{ToText(context, item)}}); + } + }; + auto do_optional = [&](const auto& maybe_item) { + if (maybe_item) { + module.push_back(At{maybe_item->loc(), + text::ModuleItem{ToText(context, *maybe_item)}}); + } + }; + + do_vector(value->types); + do_vector(value->imports); + do_vector(value->tables); + do_vector(value->memories); + do_vector(value->globals); + do_vector(value->events); + do_vector(value->exports); + do_optional(value->start); + do_vector(value->element_segments); + do_vector(value->data_segments); + + // Combine binary::Function and binary::UnpackedCode into text::Function. + assert(value->functions.size() == value->codes.size()); + for (size_t i = 0; i < value->functions.size(); ++i) { + At function = ToText(context, value->functions[i]); + module.push_back( + At{function.loc(), + text::ModuleItem{ToText(context, value->codes[i], function)}}); + } + + return At{value.loc(), module}; +} + +} // namespace wasp::convert diff --git a/test/convert/CMakeLists.txt b/test/convert/CMakeLists.txt index 32009379..ce608643 100644 --- a/test/convert/CMakeLists.txt +++ b/test/convert/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(wasp_convert_unittests ../binary/constants.cc ../text/constants.cc to_binary_test.cc + to_text_test.cc ) target_compile_options(wasp_convert_unittests diff --git a/test/convert/to_text_test.cc b/test/convert/to_text_test.cc new file mode 100644 index 00000000..a1391f87 --- /dev/null +++ b/test/convert/to_text_test.cc @@ -0,0 +1,1020 @@ +// +// Copyright 2020 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 "wasp/convert/to_text.h" + +#include "gtest/gtest.h" +#include "test/binary/constants.h" +#include "test/text/constants.h" +#include "wasp/binary/formatters.h" +#include "wasp/text/formatters.h" + +using namespace ::wasp; +using namespace ::wasp::convert; +namespace bt = ::wasp::binary::test; + +namespace { + +template +void OK(const T& expected, const B& input, Args&&... args) { + TextContext context; + auto actual = ToText(context, input, std::forward(args)...); + EXPECT_EQ(expected, actual); +} + +const SpanU8 loc1 = "A"_su8; +const SpanU8 loc2 = "B"_su8; +const SpanU8 loc3 = "C"_su8; +const SpanU8 loc4 = "D"_su8; +const SpanU8 loc5 = "E"_su8; +const SpanU8 loc6 = "F"_su8; +const SpanU8 loc7 = "G"_su8; +const SpanU8 loc8 = "H"_su8; + +// Similar to the definitions in test/text/constants, but using binary +// locations. +const text::HeapType THT_Func{At{"\x70"_su8, HeapKind::Func}}; +const text::HeapType THT_0{At{"\x00"_su8, text::Var{Index{0}}}}; +const text::ReferenceType TRT_Funcref{At{"\x70"_su8, ReferenceKind::Funcref}}; +const text::ValueType TVT_I32{At{"\x7f"_su8, NumericType::I32}}; +const text::ValueType TVT_F32{At{"\x7d"_su8, NumericType::F32}}; +const text::ValueType TVT_Funcref{At{"\x70"_su8, TRT_Funcref}}; + +} // namespace + +TEST(ConvertToTextTest, HeapType) { + // HeapKind + OK(THT_Func, At{"\x70"_su8, bt::HT_Func}); + + // Var + OK(THT_0, At{"\x00"_su8, bt::HT_0}); +} + +TEST(ConvertToTextTest, RefType) { + OK(At{loc1, text::RefType{THT_Func, At{loc2, Null::No}}}, + At{loc1, binary::RefType{bt::HT_Func, At{loc2, Null::No}}}); +} + +TEST(ConvertToTextTest, ReferenceType) { + // ReferenceKind + OK(At{loc1, text::ReferenceType{At{loc2, ReferenceKind::Funcref}}}, + At{loc1, binary::ReferenceType{At{loc2, ReferenceKind::Funcref}}}); + + // RefType + OK(At{loc1, text::ReferenceType{text::RefType{THT_Func, At{loc2, Null::No}}}}, + At{loc1, binary::ReferenceType{ + binary::RefType{bt::HT_Func, At{loc2, Null::No}}}}); +} + +TEST(ConvertToTextTest, Rtt) { + OK(At{loc1, text::Rtt{At{loc2, Index{0}}, At{loc3, THT_Func}}}, + At{loc1, binary::Rtt{At{loc2, Index{0}}, At{loc3, bt::HT_Func}}}); +} + +TEST(ConvertToTextTest, ValueType) { + // NumericKind + OK(At{loc1, TVT_I32}, At{loc1, bt::VT_I32}); + + // ReferenceType + OK(At{loc1, TVT_Funcref}, At{loc1, bt::VT_Funcref}); + + // Rtt + OK(At{loc1, text::ValueType{At{ + loc2, text::Rtt{At{loc3, Index{0}}, At{loc4, THT_Func}}}}}, + At{loc1, binary::ValueType{At{loc2, binary::Rtt{At{loc3, Index{0}}, + At{loc4, bt::HT_Func}}}}}); +} + +TEST(ConvertToTextTest, ValueTypeList) { + OK(text::ValueTypeList{text::ValueType{ + At{loc2, text::Rtt{At{loc3, Index{0}}, At{loc4, THT_Func}}}}}, + binary::ValueTypeList{binary::ValueType{ + At{loc2, binary::Rtt{At{loc3, Index{0}}, At{loc4, bt::HT_Func}}}}}); +} + +TEST(ConvertToTextTest, StorageType) { + // ValueType + OK(At{loc1, text::StorageType{At{loc2, TVT_I32}}}, + At{loc1, binary::StorageType{At{loc2, bt::VT_I32}}}); + + // PackedType + OK(At{loc1, text::StorageType{At{loc2, PackedType::I8}}}, + At{loc1, binary::StorageType{At{loc2, PackedType::I8}}}); +} + +TEST(ConvertToTextTest, IndexList) { + OK( + text::VarList{ + At{loc1, text::Var{Index{0}}}, + At{loc2, text::Var{Index{1}}}, + }, + binary::IndexList{ + At{loc1, Index{0}}, + At{loc2, Index{1}}, + }); +} + +TEST(ConvertToTextTest, FunctionType) { + OK(At{loc1, + text::FunctionType{ + text::ValueTypeList{At{loc2, TVT_I32}}, + text::ValueTypeList{At{loc3, TVT_F32}}, + }}, + At{loc1, binary::FunctionType{ + binary::ValueTypeList{At{loc2, bt::VT_I32}}, + binary::ValueTypeList{At{loc3, bt::VT_F32}}, + }}); +} + +TEST(ConvertToTextTest, FieldType) { + OK(At{loc1, + text::FieldType{nullopt, At{loc2, text::StorageType{At{loc3, TVT_I32}}}, + At{loc4, Mutability::Const}}}, + At{loc1, + binary::FieldType{At{loc2, binary::StorageType{At{loc3, bt::VT_I32}}}, + At{loc4, Mutability::Const}}}); +} + +TEST(ConvertToTextTest, FieldTypeList) { + OK(text::FieldTypeList{At{ + loc1, text::FieldType{nullopt, + At{loc2, text::StorageType{At{loc3, TVT_I32}}}, + At{loc4, Mutability::Const}}}}, + binary::FieldTypeList{At{ + loc1, + binary::FieldType{At{loc2, binary::StorageType{At{loc3, bt::VT_I32}}}, + At{loc4, Mutability::Const}}}}); +} + +TEST(ConvertToTextTest, StructType) { + OK(At{loc1, text::StructType{text::FieldTypeList{ + At{loc2, text::FieldType{nullopt, + At{loc3, text::StorageType{At{ + loc4, TVT_I32}}}, + At{loc5, Mutability::Const}}}}}}, + At{loc1, binary::StructType{binary::FieldTypeList{At{ + loc2, binary::FieldType{ + At{loc3, binary::StorageType{At{loc4, bt::VT_I32}}}, + At{loc5, Mutability::Const}}}}}}); +} + +TEST(ConvertToTextTest, ArrayType) { + OK(At{loc1, text::ArrayType{At{ + loc2, text::FieldType{nullopt, + At{loc3, text::StorageType{At{ + loc4, TVT_I32}}}, + At{loc5, Mutability::Const}}}}}, + At{loc1, binary::ArrayType{At{ + loc2, binary::FieldType{ + At{loc3, binary::StorageType{At{loc4, bt::VT_I32}}}, + At{loc5, Mutability::Const}}}}}); +} + +TEST(ConvertToTextTest, DefinedType) { + // FunctionType + OK(At{loc1, + text::DefinedType{nullopt, + At{loc2, + text::BoundFunctionType{ + text::BoundValueTypeList{text::BoundValueType{ + nullopt, At{loc3, TVT_I32}}}, + text::ValueTypeList{At{loc4, TVT_F32}}, + }}}}, + At{loc1, binary::DefinedType{ + At{loc2, binary::FunctionType{ + binary::ValueTypeList{At{loc3, bt::VT_I32}}, + binary::ValueTypeList{At{loc4, bt::VT_F32}}}}}}); + + // StructType + OK(At{loc1, + text::DefinedType{ + nullopt, + At{loc2, + text::StructType{text::FieldTypeList{At{ + loc3, + text::FieldType{ + nullopt, At{loc4, text::StorageType{At{loc5, TVT_I32}}}, + At{loc6, Mutability::Const}}}}}}}}, + At{loc1, + binary::DefinedType{At{ + loc2, + binary::StructType{binary::FieldTypeList{At{ + loc3, binary::FieldType{ + At{loc4, binary::StorageType{At{loc5, bt::VT_I32}}}, + At{loc6, Mutability::Const}}}}}}}}); + + // ArrayType + OK(At{loc1, + text::DefinedType{ + nullopt, + At{loc2, + text::ArrayType{At{ + loc3, + text::FieldType{ + nullopt, At{loc4, text::StorageType{At{loc5, TVT_I32}}}, + At{loc6, Mutability::Const}}}}}}}, + At{loc1, + binary::DefinedType{At{ + loc2, + binary::ArrayType{At{ + loc3, binary::FieldType{ + At{loc4, binary::StorageType{At{loc5, bt::VT_I32}}}, + At{loc6, Mutability::Const}}}}}}}); +} + +TEST(ConvertToTextTest, Import) { + // Function + OK(At{loc1, + text::Import{ + At{loc2, text::Text{"\"m\"", 1}}, At{loc3, text::Text{"\"n\"", 1}}, + text::FunctionDesc{nullopt, At{loc4, text::Var{Index{0}}}, {}}}}, + At{loc1, binary::Import{At{loc2, "m"_sv}, At{loc3, "n"_sv}, + At{loc4, Index{0}}}}); + + // Table + OK(At{loc1, + text::Import{ + At{loc2, text::Text{"\"m\"", 1}}, At{loc3, text::Text{"\"n\"", 1}}, + text::TableDesc{ + nullopt, At{loc4, + text::TableType{ + At{loc5, Limits{At{loc6, u32{1}}}}, + At{loc7, TRT_Funcref}, + }}}}}, + At{loc1, binary::Import{At{loc2, "m"_sv}, At{loc3, "n"_sv}, + At{loc4, binary::TableType{ + At{loc5, Limits{At{loc6, u32{1}}}}, + At{loc7, bt::RT_Funcref}, + }}}}); + + // Memory + OK(At{loc1, + text::Import{ + At{loc2, text::Text{"\"m\"", 1}}, At{loc3, text::Text{"\"n\"", 1}}, + text::MemoryDesc{nullopt, At{loc4, + MemoryType{ + At{loc5, Limits{At{loc6, u32{1}}}}, + }}}}}, + At{loc1, binary::Import{At{loc2, "m"_sv}, At{loc3, "n"_sv}, + At{loc4, MemoryType{ + At{loc5, Limits{At{loc6, u32{1}}}}, + }}}}); + + // Global + OK(At{loc1, text::Import{At{loc2, text::Text{"\"m\"", 1}}, + At{loc3, text::Text{"\"n\"", 1}}, + text::GlobalDesc{nullopt, At{loc4, + text::GlobalType{ + At{loc5, TVT_I32}, + Mutability::Const, + }}}}}, + At{loc1, binary::Import{At{loc2, "m"_sv}, At{loc3, "n"_sv}, + At{loc4, binary::GlobalType{ + At{loc5, bt::VT_I32}, + Mutability::Const, + }}}}); + + // Event + OK(At{loc1, + text::Import{ + At{loc2, text::Text{"\"m\"", 1}}, At{loc3, text::Text{"\"n\"", 1}}, + text::EventDesc{ + nullopt, + At{loc4, text::EventType{EventAttribute::Exception, + text::FunctionTypeUse{ + At{loc5, text::Var{Index{0}}}, + {}}}}}}}, + At{loc1, binary::Import{At{loc2, "m"_sv}, At{loc3, "n"_sv}, + At{loc4, binary::EventType{ + EventAttribute::Exception, + At{loc5, Index{0}}, + }}}}); +} + +TEST(ConvertToTextTest, Function) { + OK(At{loc1, text::Function{text::FunctionDesc{ + nullopt, At{loc2, text::Var{Index{13}}}, {}}, + {}, + {}, + {}}}, + At{loc1, binary::Function{At{loc2, Index{13}}}}); +} + +TEST(ConvertToTextTest, Table) { + auto binary_table_type = At{ + loc1, + binary::TableType{Limits{At{loc2, Index{0}}}, At{loc3, bt::RT_Funcref}}}; + auto text_table_type = At{ + loc1, text::TableType{Limits{At{loc2, Index{0}}}, At{loc3, TRT_Funcref}}}; + + OK(At{loc4, text::Table{text::TableDesc{nullopt, text_table_type}, {}}}, + At{loc4, binary::Table{binary_table_type}}); +} + +TEST(ConvertToTextTest, Memory) { + auto memory_type = At{loc1, MemoryType{Limits{At{loc2, Index{0}}}}}; + + OK(At{loc3, text::Memory{text::MemoryDesc{nullopt, memory_type}, {}}}, + At{loc3, binary::Memory{memory_type}}); +} + +TEST(ConvertToTextTest, Global) { + auto binary_global_type = At{loc1, binary::GlobalType{ + At{loc2, bt::VT_I32}, + At{Mutability::Const}, + }}; + + auto text_global_type = At{loc1, text::GlobalType{ + At{loc2, TVT_I32}, + At{Mutability::Const}, + }}; + + OK(At{loc3, + text::Global{text::GlobalDesc{nullopt, text_global_type}, + At{loc4, + text::ConstantExpression{ + At{loc5, text::Instruction{At{loc6, Opcode::Nop}}}, + }}, + {}}}, + At{loc3, + binary::Global{ + binary_global_type, + At{loc4, + binary::ConstantExpression{ + At{loc5, binary::Instruction{At{loc6, Opcode::Nop}}}, + }}, + }}); +} + +TEST(ConvertToTextTest, Export) { + OK(At{loc1, + text::Export{ + At{loc2, ExternalKind::Function}, + At{loc3, text::Text{"\"hello\""_sv, 5}}, + At{loc4, text::Var{Index{13}}}, + }}, + At{loc1, binary::Export{ + At{loc2, ExternalKind::Function}, + At{loc3, "hello"_sv}, + At{loc4, Index{13}}, + }}); +} + +TEST(ConvertToTextTest, Start) { + OK(At{loc1, text::Start{At{loc2, text::Var{Index{13}}}}}, + At{loc1, binary::Start{At{loc2, Index{13}}}}); +} + +TEST(ConvertToTextTest, ElementExpression) { + OK(At{loc1, text::ElementExpression{{ + At{loc2, text::Instruction{At{loc3, Opcode::Unreachable}}}, + At{loc4, text::Instruction{At{loc5, Opcode::Nop}}}, + }}}, + At{loc1, binary::ElementExpression{{ + At{loc2, binary::Instruction{At{loc3, Opcode::Unreachable}}}, + At{loc4, binary::Instruction{At{loc5, Opcode::Nop}}}, + }}}); +} + +TEST(ConvertToTextTest, ElementExpressionList) { + OK(At{loc1, + text::ElementExpressionList{ + At{loc2, + text::ElementExpression{ + At{loc3, text::Instruction{At{loc4, Opcode::Unreachable}}}}}, + At{loc5, text::ElementExpression{At{ + loc6, text::Instruction{At{loc7, Opcode::Nop}}}}}, + }}, + At{loc1, + binary::ElementExpressionList{ + At{loc2, + binary::ElementExpression{At{ + loc3, binary::Instruction{At{loc4, Opcode::Unreachable}}}}}, + At{loc5, binary::ElementExpression{At{ + loc6, binary::Instruction{At{loc7, Opcode::Nop}}}}}, + }}); +} + +TEST(ConvertToTextTest, ElementList) { + // binary::Index -> text::Var + OK(text::ElementList{text::ElementListWithVars{ + At{loc1, ExternalKind::Function}, + At{loc2, + text::VarList{ + At{loc3, text::Var{Index{0}}}, + At{loc4, text::Var{Index{1}}}, + }}, + }}, + binary::ElementList{binary::ElementListWithIndexes{ + At{loc1, ExternalKind::Function}, + At{loc2, + binary::IndexList{ + At{loc3, Index{0}}, + At{loc4, Index{1}}, + }}, + }}); + + // ElementExpression. + OK(text::ElementList{text::ElementListWithExpressions{ + At{loc1, TRT_Funcref}, + At{loc2, + text::ElementExpressionList{At{ + loc3, text::ElementExpression{At{ + loc4, text::Instruction{At{loc5, Opcode::Nop}}}}}}}}}, + binary::ElementList{binary::ElementListWithExpressions{ + At{loc1, bt::RT_Funcref}, + At{loc2, + binary::ElementExpressionList{At{ + loc3, binary::ElementExpression{At{ + loc4, binary::Instruction{At{loc5, Opcode::Nop}}}}}}}, + }}); +} + +TEST(ConvertToTextTest, ElementSegment) { + auto binary_list = binary::ElementList{ + binary::ElementListWithIndexes{At{loc1, ExternalKind::Function}, + binary::IndexList{At{loc2, Index{0}}}}, + }; + + auto text_list = text::ElementList{ + text::ElementListWithVars{At{loc1, ExternalKind::Function}, + text::VarList{At{loc2, text::Var{Index{0}}}}}, + }; + + // Active. + OK(At{loc1, + text::ElementSegment{ + nullopt, At{loc2, text::Var{Index{0}}}, + At{loc3, + text::ConstantExpression{ + At{loc4, text::Instruction{At{loc5, Opcode::Nop}}}, + }}, + text_list}}, + At{loc1, + binary::ElementSegment{ + At{loc2, Index{0}}, + At{loc3, + binary::ConstantExpression{ + At{loc4, binary::Instruction{At{loc5, Opcode::Nop}}}, + }}, + binary_list}}); + + // Passive. + OK(At{loc1, text::ElementSegment{nullopt, SegmentType::Passive, text_list}}, + At{loc1, binary::ElementSegment{SegmentType::Passive, binary_list}}); +} + +TEST(ConvertToTextTest, BlockImmediate) { + // Void inline type. + OK(At{loc1, text::BlockImmediate{nullopt, text::FunctionTypeUse{}}}, + At{loc1, binary::BlockType{binary::VoidType{}}}); + + // Single inline type. + OK(At{loc1, + text::BlockImmediate{ + nullopt, + text::FunctionTypeUse{nullopt, text::FunctionType{{}, {TVT_I32}}}}}, + At{loc1, binary::BlockType{bt::VT_I32}}); + + // Generic type (via multi-value proposal). + OK(At{loc1, + text::BlockImmediate{ + nullopt, text::FunctionTypeUse{text::Var{Index{13}}, + text::FunctionType{}}}}, + At{loc1, binary::BlockType(13)}); +} + +TEST(ConvertToTextTest, BrOnExnImmediate) { + OK(At{loc1, text::BrOnExnImmediate{At{loc2, text::Var{Index{13}}}, + At{loc3, text::Var{Index{14}}}}}, + At{loc1, + binary::BrOnExnImmediate{At{loc2, Index{13}}, At{loc3, Index{14}}}}); +} + +TEST(ConvertToTextTest, BrTableImmediate) { + OK(At{loc1, text::BrTableImmediate{{At{loc2, text::Var{Index{13}}}}, + At{loc3, text::Var{Index{14}}}}}, + At{loc1, + binary::BrTableImmediate{{At{loc2, Index{13}}}, At{loc3, Index{14}}}}); +} + +TEST(ConvertToTextTest, CallIndirectImmediate) { + OK(At{loc1, + text::CallIndirectImmediate{ + At{loc2, text::Var{Index{14}}}, + text::FunctionTypeUse{At{loc3, text::Var{Index{13}}}, {}}}}, + At{loc1, binary::CallIndirectImmediate{At{loc3, Index{13}}, + At{loc2, Index{14}}}}); +} + +TEST(ConvertToTextTest, CopyImmediate) { + // dst and src defined. + OK(At{loc1, text::CopyImmediate{At{loc2, text::Var{Index{13}}}, + At{loc3, text::Var{Index{14}}}}}, + At{loc1, binary::CopyImmediate{At{loc2, Index{13}}, At{loc3, Index{14}}}}); +} + +TEST(ConvertToTextTest, FuncBindImmediate) { + OK(At{loc1, text::FuncBindImmediate{At{loc2, text::Var{Index{13}}}, {}}}, + At{loc1, binary::FuncBindImmediate{At{loc2, Index{13}}}}); +} + +TEST(ConvertToTextTest, HeapType2Immediate) { + OK(At{loc1, text::HeapType2Immediate{At{loc2, THT_Func}, At{loc3, THT_Func}}}, + At{loc1, binary::HeapType2Immediate{At{loc2, bt::HT_Func}, + At{loc3, bt::HT_Func}}}); +} + +TEST(ConvertToTextTest, InitImmediate) { + OK(At{loc1, text::InitImmediate{At{loc2, text::Var{Index{13}}}, + At{loc3, text::Var{Index{14}}}}}, + At{loc1, binary::InitImmediate{At{loc2, Index{13}}, At{loc3, Index{14}}}}); +} + +TEST(ConvertToTextTest, LetImmediate) { + // Empty let immediate. + OK(At{loc1, text::LetImmediate{}}, + At{loc1, binary::LetImmediate{binary::BlockType{binary::VoidType{}}, {}}}); + + // Let immediate with locals. + OK(At{loc1, + text::LetImmediate{ + text::BlockImmediate{}, + {At{loc2, + text::BoundValueTypeList{ + text::BoundValueType{nullopt, At{loc3, TVT_I32}}, + text::BoundValueType{nullopt, At{loc3, TVT_I32}}, + }}}}}, + At{loc1, binary::LetImmediate{binary::BlockType{binary::VoidType{}}, + At{loc2, binary::LocalsList{binary::Locals{ + 2, At{loc3, bt::VT_I32}}}}}}); +} + +TEST(ConvertToTextTest, MemArgImmediate) { + u32 align = 8; + u32 align_log2 = 3; + u32 offset = 5; + + // align and offset defined. + OK(At{loc1, text::MemArgImmediate{At{loc2, align}, At{loc3, offset}}}, + At{loc1, binary::MemArgImmediate{At{loc2, align_log2}, At{loc3, offset}}}); +} + +TEST(ConvertToTextTest, StructFieldImmediate) { + OK(At{loc1, text::StructFieldImmediate{At{loc2, text::Var{Index{13}}}, + At{loc3, text::Var{Index{14}}}}}, + At{loc1, binary::StructFieldImmediate{At{loc2, Index{13}}, + At{loc3, Index{14}}}}); +} + +TEST(ConvertToTextTest, Instruction) { + // Bare. + OK(At{loc1, text::Instruction{At{loc2, Opcode::Nop}}}, + At{loc1, binary::Instruction{At{loc2, Opcode::Nop}}}); + + // s32. + OK(At{loc1, text::Instruction{At{loc2, Opcode::I32Const}, At{loc3, s32{0}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::I32Const}, At{loc3, s32{0}}}}); + + // s64. + OK(At{loc1, text::Instruction{At{loc2, Opcode::I64Const}, At{loc3, s64{0}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::I64Const}, At{loc3, s64{0}}}}); + + // f32. + OK(At{loc1, text::Instruction{At{loc2, Opcode::F32Const}, At{loc3, f32{0}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::F32Const}, At{loc3, f32{0}}}}); + + // f64. + OK(At{loc1, text::Instruction{At{loc2, Opcode::F64Const}, At{loc3, f64{0}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::F64Const}, At{loc3, f64{0}}}}); + + // v128. + OK(At{loc1, text::Instruction{At{loc2, Opcode::V128Const}, At{loc3, v128{}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::V128Const}, At{loc3, v128{}}}}); + + // Var. + OK(At{loc1, text::Instruction{At{loc2, Opcode::LocalGet}, + At{loc3, text::Var{Index{13}}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::LocalGet}, At{loc3, Index{13}}}}); + + // BlockImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::Block}, + text::BlockImmediate{nullopt, + text::FunctionTypeUse{text::Var{Index{13}}, + text::FunctionType{}}}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::Block}, binary::BlockType{13}}}); + + // BrOnExnImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::BrOnExn}, + At{loc3, text::BrOnExnImmediate{At{loc4, text::Var{Index{13}}}, + At{loc5, text::Var{Index{14}}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::BrOnExn}, + At{loc3, binary::BrOnExnImmediate{At{loc4, Index{13}}, + At{loc5, Index{14}}}}}}); + + // BrTableImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::BrTable}, + At{loc3, text::BrTableImmediate{{At{loc4, text::Var{Index{13}}}}, + At{loc5, text::Var{Index{14}}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::BrTable}, + At{loc3, binary::BrTableImmediate{{At{loc4, Index{13}}}, + At{loc5, Index{14}}}}}}); + + // CallIndirectImmediate. + OK(At{loc1, + text::Instruction{At{loc2, Opcode::CallIndirect}, + At{loc3, + text::CallIndirectImmediate{ + At{loc4, text::Var{Index{14}}}, + text::FunctionTypeUse{ + At{loc5, text::Var{Index{13}}}, {}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::CallIndirect}, + At{loc3, binary::CallIndirectImmediate{ + At{loc5, Index{13}}, At{loc4, Index{14}}}}}}); + + // CopyImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::MemoryCopy}, + At{loc3, text::CopyImmediate{At{loc4, text::Var{Index{13}}}, + At{loc5, text::Var{Index{14}}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::MemoryCopy}, + At{loc3, binary::CopyImmediate{At{loc4, Index{13}}, + At{loc5, Index{14}}}}}}); + + // FuncBindImmediate + OK(At{loc1, text::Instruction{At{loc2, Opcode::FuncBind}, + At{loc3, + text::FuncBindImmediate{ + At{loc4, text::Var{Index{13}}}, {}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::FuncBind}, + At{loc3, binary::FuncBindImmediate{At{loc4, Index{13}}}}}}); + + // FuncBindImmediate + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::FuncBind}, + At{loc3, text::FuncBindImmediate{text::FunctionTypeUse{ + At{loc4, text::Var{Index{13}}}, {}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::FuncBind}, + At{loc3, binary::FuncBindImmediate{At{loc4, Index{13}}}}}}); + + // InitImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::TableInit}, + At{loc3, text::InitImmediate{At{loc4, text::Var{Index{13}}}, + At{loc5, text::Var{Index{14}}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::TableInit}, + At{loc3, binary::InitImmediate{At{loc4, Index{13}}, + At{loc5, Index{14}}}}}}); + + // LetImmediate. + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::Let}, + At{loc3, + text::LetImmediate{ + text::BlockImmediate{ + nullopt, + text::FunctionTypeUse{text::Var{Index{15}}, {}}}, + {At{loc4, + text::BoundValueTypeList{ + text::BoundValueType{nullopt, At{loc6, TVT_I32}}, + text::BoundValueType{nullopt, At{loc6, TVT_I32}}, + }}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::Let}, + At{loc3, binary::LetImmediate{ + binary::BlockType{15}, + At{loc4, binary::LocalsList{binary::Locals{ + 2, At{loc6, bt::VT_I32}}}}}}}}); + + // MemArgImmediate. + OK(At{loc1, + text::Instruction{At{loc2, Opcode::I32Load}, + At{loc3, text::MemArgImmediate{At{loc4, u32{4}}, + At{loc5, u32{13}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::I32Load}, + At{loc3, binary::MemArgImmediate{At{loc4, u32{2}}, + At{loc5, u32{13}}}}}}); + + // HeapType. + OK(At{loc1, text::Instruction{At{loc2, Opcode::RefNull}, At{loc3, THT_Func}}}, + At{loc1, + binary::Instruction{At{loc2, Opcode::RefNull}, At{loc3, bt::HT_Func}}}); + + // SelectImmediate. + OK(At{loc1, + text::Instruction{At{loc2, Opcode::SelectT}, + At{loc3, text::SelectImmediate{At{loc4, TVT_I32}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::SelectT}, + At{loc3, binary::SelectImmediate{At{loc4, bt::VT_I32}}}}}); + + // ShuffleImmediate. + OK(At{loc1, text::Instruction{At{loc2, Opcode::I8X16Shuffle}, + At{loc3, ShuffleImmediate{}}}}, + At{loc1, binary::Instruction{At{loc2, Opcode::I8X16Shuffle}, + At{loc3, ShuffleImmediate{}}}}); + + // SimdLaneImmediate. + OK(At{loc1, text::Instruction{At{loc2, Opcode::I8X16ExtractLaneS}, + At{loc3, text::SimdLaneImmediate{13}}}}, + At{loc1, binary::Instruction{At{loc2, Opcode::I8X16ExtractLaneS}, + At{loc3, binary::SimdLaneImmediate{13}}}}); + + // HeapType2Immediate + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::RefTest}, + At{loc3, text::HeapType2Immediate{At{loc4, THT_Func}, + At{loc5, THT_Func}}}}}, + At{loc1, binary::Instruction{At{loc2, Opcode::RefTest}, + At{loc3, binary::HeapType2Immediate{ + At{loc4, bt::HT_Func}, + At{loc5, bt::HT_Func}}}}}); + + // StructFieldImmediate + OK(At{loc1, + text::Instruction{ + At{loc2, Opcode::StructGet}, + At{loc3, + text::StructFieldImmediate{At{loc4, text::Var{Index{13}}}, + At{loc5, text::Var{Index{14}}}}}}}, + At{loc1, binary::Instruction{ + At{loc2, Opcode::StructGet}, + At{loc3, binary::StructFieldImmediate{ + At{loc4, Index{13}}, At{loc5, Index{14}}}}}}); +} + +TEST(ConvertToTextTest, LocalsList) { + OK( + text::BoundValueTypeList{ + text::BoundValueType{nullopt, At{loc2, TVT_I32}}, + text::BoundValueType{nullopt, At{loc2, TVT_I32}}, + text::BoundValueType{nullopt, At{loc3, TVT_F32}}, + }, + binary::LocalsList{binary::Locals{2, At{loc2, bt::VT_I32}}, + binary::Locals{1, At{loc3, bt::VT_F32}}}); +} + +TEST(ConvertToTextTest, DataSegment) { + // Active. + OK(At{loc1, + text::DataSegment{ + nullopt, At{loc2, text::Var{Index{13}}}, + At{loc3, text::ConstantExpression{At{ + loc4, text::Instruction{At{loc5, Opcode::Nop}}}}}, + text::TextList{text::Text{"\"hello\\00\"", 6}}}}, + At{loc1, + binary::DataSegment{ + At{loc2, Index{13}}, + At{loc3, binary::ConstantExpression{At{ + loc4, binary::Instruction{At{loc5, Opcode::Nop}}}}}, + "hello\x00"_su8}}); +} + +TEST(ConvertToTextTest, EventType) { + OK(At{loc1, + text::EventType{ + EventAttribute::Exception, + text::FunctionTypeUse{ + At{loc2, text::Var{Index{0}}}, + {}, + }, + }}, + At{loc1, binary::EventType{ + EventAttribute::Exception, + At{loc2, Index{0}}, + }}); +} + +TEST(ConvertToTextTest, Event) { + OK(At{loc1, + text::Event{text::EventDesc{ + nullopt, At{loc2, + text::EventType{ + EventAttribute::Exception, + text::FunctionTypeUse{ + At{loc3, text::Var{Index{0}}}, + {}, + }, + }}}, + {}}}, + At{loc1, binary::Event{At{loc2, binary::EventType{ + EventAttribute::Exception, + At{loc3, Index{0}}, + }}}}); +} + +TEST(ConvertToTextTest, Module) { + // Additional locations only needed for Module. :-) + const SpanU8 loc9 = "I"_su8; + const SpanU8 loc10 = "J"_su8; + const SpanU8 loc11 = "K"_su8; + const SpanU8 loc12 = "L"_su8; + const SpanU8 loc13 = "M"_su8; + const SpanU8 loc14 = "N"_su8; + const SpanU8 loc15 = "O"_su8; + const SpanU8 loc16 = "P"_su8; + const SpanU8 loc17 = "Q"_su8; + const SpanU8 loc18 = "R"_su8; + const SpanU8 loc19 = "S"_su8; + const SpanU8 loc20 = "T"_su8; + const SpanU8 loc21 = "U"_su8; + const SpanU8 loc22 = "V"_su8; + const SpanU8 loc23 = "W"_su8; + const SpanU8 loc24 = "X"_su8; + const SpanU8 loc25 = "Y"_su8; + const SpanU8 loc26 = "Z"_su8; + const SpanU8 loc27 = "AA"_su8; + const SpanU8 loc28 = "BB"_su8; + + auto binary_table_type = + At{"T0"_su8, binary::TableType{At{"T1"_su8, Limits{At{"T2"_su8, u32{0}}}}, + At{"T3"_su8, bt::RT_Funcref}}}; + + auto text_table_type = + At{"T0"_su8, text::TableType{At{"T1"_su8, Limits{At{"T2"_su8, u32{0}}}}, + At{"T3"_su8, TRT_Funcref}}}; + + auto memory_type = + At{"M0"_su8, MemoryType{At{"M1"_su8, Limits{At{"M2"_su8, u32{0}}}}}}; + + auto binary_global_type = + At{"G0"_su8, binary::GlobalType{At{"G1"_su8, bt::VT_I32}, + At{"G2"_su8, Mutability::Const}}}; + + auto text_global_type = At{ + "G0"_su8, + text::GlobalType{At{"G1"_su8, TVT_I32}, At{"G2"_su8, Mutability::Const}}}; + + auto external_kind = At{"EK"_su8, ExternalKind::Function}; + + // These will be shared between global, data, and element segments. This + // would never actually happen, but it simplifies the test below. + auto binary_constant_expression = + At{"CE0"_su8, + binary::ConstantExpression{ + At{"CE1"_su8, binary::Instruction{At{"CE2"_su8, Opcode::I32Const}, + At{"CE3"_su8, s32{0}}}}}}; + + auto text_constant_expression = + At{"CE0"_su8, + text::ConstantExpression{ + At{"CE1"_su8, text::Instruction{At{"CE2"_su8, Opcode::I32Const}, + At{"CE3"_su8, s32{0}}}}}}; + + OK(At{loc1, + text::Module{ + // (type (func)) + text::ModuleItem{At{ + loc2, text::DefinedType{nullopt, text::BoundFunctionType{}}}}, + // (import "m" "n" (func (type 0))) + text::ModuleItem{At{ + loc3, + text::Import{At{loc4, text::Text{"\"m\"", 1}}, + At{loc5, text::Text{"\"n\"", 1}}, + text::FunctionDesc{ + nullopt, At{loc6, text::Var{Index{0}}}, {}}}}}, + // (table 0 funcref) + text::ModuleItem{ + At{loc9, + text::Table{text::TableDesc{nullopt, text_table_type}, {}}}}, + // (memory 0) + text::ModuleItem{ + At{loc10, + text::Memory{text::MemoryDesc{nullopt, memory_type}, {}}}}, + // (global i32 i32.const 0) + text::ModuleItem{At{ + loc11, text::Global{text::GlobalDesc{nullopt, text_global_type}, + text_constant_expression, + {}}}}, + // (event) + text::ModuleItem{ + At{loc12, + text::Event{ + text::EventDesc{ + nullopt, + At{loc13, + text::EventType{ + EventAttribute::Exception, + text::FunctionTypeUse{ + At{loc14, text::Var{Index{0}}}, {}}}}}, + {}}}}, + // (export "e" (func 0)) + text::ModuleItem{ + At{loc15, text::Export{external_kind, + At{loc16, text::Text{"\"e\""_sv, 1}}, + At{loc17, text::Var{Index{0}}}}}}, + // (start 0) + text::ModuleItem{ + At{loc18, text::Start{At{loc19, text::Var{Index{0}}}}}}, + // (elem (i32.const 0) func 0) + text::ModuleItem{ + At{loc20, + text::ElementSegment{ + nullopt, At{loc21, text::Var{Index{0}}}, + text_constant_expression, + text::ElementList{text::ElementListWithVars{ + external_kind, {At{loc22, text::Var{Index{0}}}}}}}}}, + // (data (i32.const 0) "hello") + text::ModuleItem{ + At{loc23, + text::DataSegment{ + nullopt, At{loc24, text::Var{Index{0}}}, + text_constant_expression, + text::TextList{text::Text{"\"hello\""_sv, 5}}}}}, + // (func (type 0) nop) + text::ModuleItem{ + At{loc7, + text::Function{ + text::FunctionDesc{ + nullopt, At{loc8, text::Var{Index{0}}}, {}}, + {}, + {At{loc25, text::Instruction{At{loc26, Opcode::Nop}}}, + At{loc27, text::Instruction{At{loc28, Opcode::End}}}}, + {}}}}, + }}, + At{loc1, + binary::Module{ + // types + {At{loc2, binary::DefinedType{binary::FunctionType{}}}}, + // imports + {At{loc3, binary::Import{At{loc4, "m"_sv}, At{loc5, "n"_sv}, + At{loc6, Index{0}}}}}, + // functions + {At{loc7, binary::Function{At{loc8, Index{0}}}}}, + // tables + {At{loc9, binary::Table{binary_table_type}}}, + // memories + {At{loc10, binary::Memory{memory_type}}}, + // globals + {At{loc11, binary::Global{binary_global_type, + binary_constant_expression}}}, + // events + {At{loc12, binary::Event{At{ + loc13, binary::EventType{EventAttribute::Exception, + At{loc14, Index{0}}}}}}}, + // exports + {At{loc15, binary::Export{external_kind, At{loc16, "e"_sv}, + At{loc17, Index{0}}}}}, + // starts + {At{loc18, binary::Start{At{loc19, Index{0}}}}}, + // element_segments + {At{loc20, + binary::ElementSegment{ + At{loc21, Index{0}}, binary_constant_expression, + binary::ElementList{binary::ElementListWithIndexes{ + external_kind, {At{loc22, Index{0}}}}}}}}, + // data_counts + {binary::DataCount{Index{1}}}, + // codes + {At{loc7, + binary::UnpackedCode{ + binary::LocalsList{}, + binary::UnpackedExpression{binary::InstructionList{ + At{loc25, binary::Instruction{At{loc26, Opcode::Nop}}}, + At{loc27, binary::Instruction{At{loc28, Opcode::End}}}, + }}}}}, + // data_segments + {At{loc23, + binary::DataSegment{At{loc24, Index{0}}, + binary_constant_expression, "hello"_su8}}}, + }}); +}