| /* | |
| * 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. | |
| */ | |
| // | |
| // Print out text in s-expression format | |
| // | |
| #include <algorithm> | |
| #include <fstream> | |
| #include <ir/iteration.h> | |
| #include <ir/module-utils.h> | |
| #include <ir/table-utils.h> | |
| #include <ir/utils.h> | |
| #include <pass.h> | |
| #include <pretty_printing.h> | |
| #include <support/string.h> | |
| #include <wasm-annotations.h> | |
| #include <wasm-stack.h> | |
| #include <wasm-type-printing.h> | |
| #include <wasm.h> | |
| namespace wasm { | |
| struct PrintSExpression; | |
| static std::ostream& printExpression(Expression* expression, | |
| std::ostream& o, | |
| bool minify = false, | |
| bool full = false, | |
| Module* wasm = nullptr); | |
| static std::ostream& | |
| printStackInst(StackInst* inst, std::ostream& o, Function* func = nullptr); | |
| static std::ostream& printStackIR(StackIR* ir, PrintSExpression&); | |
| namespace { | |
| bool checkIsFullForced() { | |
| if (getenv("BINARYEN_PRINT_FULL")) { | |
| return std::stoi(getenv("BINARYEN_PRINT_FULL")) != 0; | |
| } | |
| return false; | |
| } | |
| bool isFullForced() { | |
| static bool full = checkIsFullForced(); | |
| return full; | |
| } | |
| std::ostream& printMemoryName(Name name, std::ostream& o, Module* wasm) { | |
| if (!wasm || wasm->memories.size() > 1) { | |
| o << ' '; | |
| name.print(o); | |
| } | |
| return o; | |
| } | |
| std::ostream& printLocal(Index index, Function* func, std::ostream& o) { | |
| Name name; | |
| if (func) { | |
| name = func->getLocalNameOrDefault(index); | |
| } | |
| if (!name) { | |
| name = Name::fromInt(index); | |
| } | |
| return name.print(o); | |
| } | |
| // Print a name from the type section, if available. Otherwise print the type | |
| // normally. | |
| void printTypeOrName(Type type, std::ostream& o, Module* wasm) { | |
| struct Printer : TypeNameGeneratorBase<Printer> { | |
| Module* wasm; | |
| DefaultTypeNameGenerator fallback; | |
| Printer(Module* wasm) : wasm(wasm) {} | |
| TypeNames getNames(HeapType type) { | |
| if (wasm) { | |
| if (auto it = wasm->typeNames.find(type); it != wasm->typeNames.end()) { | |
| return it->second; | |
| } | |
| } | |
| return fallback.getNames(type); | |
| } | |
| } print{wasm}; | |
| o << print(type); | |
| } | |
| } // anonymous namespace | |
| // Printing "unreachable" as a instruction prefix type is not valid in wasm text | |
| // format. Print something else to make it pass. | |
| static Type forceConcrete(Type type) { | |
| return type.isConcrete() ? type : Type::i32; | |
| } | |
| // Whatever type we print must be valid for the alignment. | |
| static Type forceConcrete(Type type, Index align) { | |
| return type.isConcrete() ? type | |
| : align >= 16 ? Type::v128 | |
| : align >= 8 ? Type::i64 | |
| : Type::i32; | |
| } | |
| struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> { | |
| std::ostream& o; | |
| unsigned indent = 0; | |
| bool minify; | |
| const char* maybeSpace; | |
| const char* maybeNewLine; | |
| // Whether to not elide nodes in output when possible (like implicit blocks) | |
| // and to emit types. | |
| bool full = false; | |
| // If present, it contains StackIR that we will print. | |
| std::optional<ModuleStackIR> moduleStackIR; | |
| Module* currModule = nullptr; | |
| Function* currFunction = nullptr; | |
| // Keep track of the last printed debug location to avoid printing | |
| // repeated debug locations for children. nullopt means that we have | |
| // not yet printed any debug location, or that we last printed an | |
| // annotation indicating that the expression had no associated | |
| // debug location. | |
| std::optional<Function::DebugLocation> lastPrintedLocation; | |
| bool debugInfo; | |
| // Used to print delegate's depth argument when it throws to the caller | |
| int controlFlowDepth = 0; | |
| std::vector<HeapType> heapTypes; | |
| std::unordered_map<Signature, HeapType> signatureTypes; | |
| // Track the print indent so that we can see when it changes. That affects how | |
| // we print debug annotations. In particular, we don't want to print repeated | |
| // debug locations for children, like this: | |
| // | |
| // ;;@ file.cpp:20:4 | |
| // (block | |
| // ;; no need to annotate here; children have the parent's location by | |
| // ;; default anyhow | |
| // (nop) | |
| // | |
| // But we do want to print an annotation even if it repeats if it is not a | |
| // child: | |
| // | |
| // ;;@ file.cpp:20:4 | |
| // (block) | |
| // ;;@ file.cpp:20:4 - this is clearer to annotate, to avoid confusion with | |
| // the case where there is no debug info on the nop | |
| // (nop) | |
| // | |
| unsigned lastPrintIndent = 0; | |
| // Print type names by saved name or index if we have a module, or otherwise | |
| // by generating minimalist names. TODO: Handle conflicts between | |
| // user-provided names and the fallback indexed names. | |
| struct TypePrinter : TypeNameGeneratorBase<TypePrinter> { | |
| PrintSExpression& parent; | |
| DefaultTypeNameGenerator fallback; | |
| std::unordered_map<HeapType, TypeNames> fallbackNames; | |
| TypePrinter(PrintSExpression& parent, const std::vector<HeapType>& types) | |
| : parent(parent) { | |
| if (!parent.currModule) { | |
| return; | |
| } | |
| std::unordered_set<Name> usedNames; | |
| for (auto& [_, names] : parent.currModule->typeNames) { | |
| usedNames.insert(names.name); | |
| } | |
| size_t i = 0; | |
| // Use indices for any remaining type names, skipping any that are already | |
| // used. | |
| for (auto type : types) { | |
| if (parent.currModule->typeNames.contains(type)) { | |
| ++i; | |
| continue; | |
| } | |
| Name name; | |
| do { | |
| name = std::to_string(i++); | |
| } while (usedNames.contains(name)); | |
| fallbackNames[type] = {name, {}}; | |
| } | |
| } | |
| TypeNames getNames(HeapType type) { | |
| if (parent.currModule) { | |
| if (auto it = parent.currModule->typeNames.find(type); | |
| it != parent.currModule->typeNames.end()) { | |
| return it->second; | |
| } | |
| // In principle we should always have at least a fallback name for every | |
| // type in the module, so this lookup should never fail. In practice, | |
| // though, the `printExpression` variants deliberately avoid walking the | |
| // module to find unnamed types so they can be safely used in a | |
| // function-parallel context. That means we can have a module but not | |
| // have generated the fallback names, so this lookup can fail, in which | |
| // case we generate a name on demand. | |
| if (auto it = fallbackNames.find(type); it != fallbackNames.end()) { | |
| return it->second; | |
| } | |
| } | |
| return fallback.getNames(type); | |
| } | |
| Name getName(HeapType type) { return getNames(type).name; } | |
| } typePrinter; | |
| PrintSExpression(std::ostream& o) : o(o), typePrinter(*this, heapTypes) { | |
| setMinify(false); | |
| if (!full) { | |
| full = isFullForced(); | |
| } | |
| } | |
| void setModule(Module* module); | |
| std::ostream& printType(Type type) { return o << typePrinter(type); } | |
| std::ostream& printHeapTypeName(HeapType type) { | |
| if (type.isBasic()) { | |
| return o << type; | |
| } | |
| return typePrinter.getNames(type).name.print(o); | |
| } | |
| std::ostream& printPrefixedTypes(const char* prefix, Type type); | |
| std::ostream& printResultType(Type type) { | |
| return printPrefixedTypes("result", type); | |
| } | |
| std::ostream& printParamType(Type type) { | |
| return printPrefixedTypes("param", type); | |
| } | |
| std::ostream& printBlockType(Signature sig) { | |
| assert(sig.params == Type::none); | |
| if (sig.results == Type::none) { | |
| return o; | |
| } | |
| if (sig.results.isTuple()) { | |
| if (auto it = signatureTypes.find(sig); it != signatureTypes.end()) { | |
| o << "(type "; | |
| printHeapTypeName(it->second); | |
| o << ") "; | |
| } | |
| } | |
| printResultType(sig.results); | |
| return o; | |
| } | |
| void | |
| printDebugLocation(const std::optional<Function::DebugLocation>& location); | |
| // Prints debug info for a delimiter in an expression. | |
| void printDebugDelimiterLocation(Expression* curr, Index i); | |
| // Prints debug info and code annotations. | |
| void printMetadata(Expression* curr); | |
| // Print code annotations for an expression. If the expression is nullptr, | |
| // prints for the current function. | |
| void printCodeAnnotations(Expression* curr); | |
| void printCodeAnnotations(const CodeAnnotation& annotation); | |
| void printExpressionContents(Expression* curr); | |
| void visit(Expression* curr) { | |
| printMetadata(curr); | |
| UnifiedExpressionVisitor<PrintSExpression>::visit(curr); | |
| } | |
| void setMinify(bool minify_) { | |
| minify = minify_; | |
| maybeSpace = minify ? "" : " "; | |
| maybeNewLine = minify ? "" : "\n"; | |
| } | |
| void setFull(bool full_) { full = full_; } | |
| void generateStackIR(const PassOptions& options) { | |
| moduleStackIR.emplace(*currModule, options); | |
| } | |
| void setDebugInfo(bool debugInfo_) { debugInfo = debugInfo_; } | |
| void incIndent(); | |
| void decIndent(); | |
| void printFullLine(Expression* expression); | |
| // loop, if, and try can contain implicit blocks. But they are not needed to | |
| // be printed in some cases. | |
| void maybePrintImplicitBlock(Expression* curr); | |
| // Generic visitor, overridden only when necessary. | |
| void visitExpression(Expression* curr); | |
| void visitBlock(Block* curr); | |
| void visitIf(If* curr); | |
| void visitLoop(Loop* curr); | |
| void visitTry(Try* curr); | |
| void visitTryTable(TryTable* curr); | |
| void printUnreachableReplacement(Expression* curr); | |
| // Module-level visitors | |
| void handleSignature(Function* curr, bool printImplicitNames = false); | |
| void visitExport(Export* curr); | |
| void emitImportHeader(Importable* curr); | |
| void visitGlobal(Global* curr); | |
| void emitGlobalType(Global* curr); | |
| void visitImportedGlobal(Global* curr); | |
| void visitDefinedGlobal(Global* curr); | |
| void visitFunction(Function* curr); | |
| void visitImportedFunction(Function* curr); | |
| void visitDefinedFunction(Function* curr); | |
| void visitTag(Tag* curr); | |
| void visitImportedTag(Tag* curr); | |
| void visitDefinedTag(Tag* curr); | |
| void printTagType(HeapType type); | |
| void printTableHeader(Table* curr); | |
| void visitTable(Table* curr); | |
| void visitElementSegment(ElementSegment* curr); | |
| void printMemoryHeader(Memory* curr); | |
| void visitMemory(Memory* curr); | |
| void visitDataSegment(DataSegment* curr); | |
| void printDylinkSection(const std::unique_ptr<DylinkSection>& dylinkSection); | |
| void visitModule(Module* curr); | |
| }; | |
| // Prints the internal contents of an expression: everything but | |
| // the children. | |
| struct PrintExpressionContents | |
| : public OverriddenVisitor<PrintExpressionContents> { | |
| PrintSExpression& parent; | |
| Module* wasm = nullptr; | |
| Function* currFunction = nullptr; | |
| std::ostream& o; | |
| FeatureSet features; | |
| bool full; | |
| PrintExpressionContents(PrintSExpression& parent) | |
| : parent(parent), wasm(parent.currModule), | |
| currFunction(parent.currFunction), o(parent.o), | |
| features(wasm ? wasm->features : FeatureSet::All), full(isFullForced()) {} | |
| std::ostream& printType(Type type) { return parent.printType(type); } | |
| std::ostream& printHeapTypeName(HeapType type) { | |
| return parent.printHeapTypeName(type); | |
| } | |
| std::ostream& printResultType(Type type) { | |
| return parent.printResultType(type); | |
| } | |
| std::ostream& printParamType(Type type) { | |
| return parent.printParamType(type); | |
| } | |
| std::ostream& printBlockType(Signature sig) { | |
| return parent.printBlockType(sig); | |
| } | |
| void printMemoryPostfix(uint8_t bytes, Type type) { | |
| switch (bytes) { | |
| case 1: | |
| o << '8'; | |
| break; | |
| case 2: | |
| if (type == Type::f32) { | |
| o << "_f16"; | |
| } else { | |
| o << "16"; | |
| } | |
| break; | |
| case 4: | |
| o << "32"; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| } | |
| std::ostream& printStorePostfix(uint8_t bytes, Type valueType) { | |
| if (bytes < 4 || (valueType == Type::i64 && bytes < 8)) { | |
| printMemoryPostfix(bytes, valueType); | |
| } | |
| return o; | |
| } | |
| void visitBlock(Block* curr) { | |
| printMedium(o, "block"); | |
| if (curr->name.is()) { | |
| o << ' '; | |
| curr->name.print(o); | |
| } | |
| if (curr->type.isConcrete()) { | |
| o << ' '; | |
| printBlockType(Signature(Type::none, curr->type)); | |
| } | |
| } | |
| void visitIf(If* curr) { | |
| printMedium(o, "if"); | |
| // Ifs are unreachable if their condition is unreachable, but in that case | |
| // the arms might have some concrete type we have to account for to produce | |
| // valid wat. | |
| auto type = curr->type; | |
| if (curr->condition->type == Type::unreachable && curr->ifFalse) { | |
| type = Type::getLeastUpperBound(curr->ifTrue->type, curr->ifFalse->type); | |
| } | |
| if (type.isConcrete()) { | |
| o << ' '; | |
| printBlockType(Signature(Type::none, type)); | |
| } | |
| } | |
| void visitLoop(Loop* curr) { | |
| printMedium(o, "loop"); | |
| if (curr->name.is()) { | |
| o << ' '; | |
| curr->name.print(o); | |
| } | |
| if (curr->type.isConcrete()) { | |
| o << ' '; | |
| printBlockType(Signature(Type::none, curr->type)); | |
| } | |
| } | |
| void visitBreak(Break* curr) { | |
| if (curr->condition) { | |
| printMedium(o, "br_if "); | |
| } else { | |
| printMedium(o, "br "); | |
| } | |
| curr->name.print(o); | |
| } | |
| void visitSwitch(Switch* curr) { | |
| printMedium(o, "br_table"); | |
| for (auto& t : curr->targets) { | |
| o << ' '; | |
| t.print(o); | |
| } | |
| o << ' '; | |
| curr->default_.print(o); | |
| } | |
| void visitCall(Call* curr) { | |
| if (curr->isReturn) { | |
| printMedium(o, "return_call "); | |
| } else { | |
| printMedium(o, "call "); | |
| } | |
| curr->target.print(o); | |
| } | |
| void visitCallIndirect(CallIndirect* curr) { | |
| if (curr->isReturn) { | |
| printMedium(o, "return_call_indirect "); | |
| } else { | |
| printMedium(o, "call_indirect "); | |
| } | |
| // Even if reference-types is not enabled because the features section or | |
| // the matching command-line flags are not present, if the table index is | |
| // greater than 0, we print the table because otherwise the results will be | |
| // incorrect. | |
| if (features.hasReferenceTypes() || | |
| (wasm && !wasm->tables.empty() && | |
| wasm->tables[0]->name != curr->table)) { | |
| curr->table.print(o); | |
| o << ' '; | |
| } | |
| o << '('; | |
| printMinor(o, "type "); | |
| printHeapTypeName(curr->heapType); | |
| o << ')'; | |
| } | |
| void visitLocalGet(LocalGet* curr) { | |
| printMedium(o, "local.get "); | |
| printLocal(curr->index, currFunction, o); | |
| } | |
| void visitLocalSet(LocalSet* curr) { | |
| // Print unreachable tees as sets. This makes the output valid WebAssembly | |
| // in more cases because it avoids pushing a concrete type (which may not | |
| // be the type required by the next instruction) onto a polymorphic stack. | |
| if (curr->isTee() && curr->type != Type::unreachable) { | |
| printMedium(o, "local.tee "); | |
| } else { | |
| printMedium(o, "local.set "); | |
| } | |
| printLocal(curr->index, currFunction, o); | |
| if (full && currFunction) { | |
| o << " (; local type: "; | |
| printType(currFunction->getLocalType(curr->index)); | |
| o << " ;)"; | |
| } | |
| } | |
| void visitGlobalGet(GlobalGet* curr) { | |
| printMedium(o, "global.get "); | |
| curr->name.print(o); | |
| } | |
| void visitGlobalSet(GlobalSet* curr) { | |
| printMedium(o, "global.set "); | |
| curr->name.print(o); | |
| } | |
| void visitLoad(Load* curr) { | |
| prepareColor(o) << forceConcrete(curr->type, curr->align); | |
| if (curr->isAtomic()) { | |
| o << ".atomic"; | |
| } | |
| o << ".load"; | |
| if (curr->type != Type::unreachable && | |
| curr->bytes < curr->type.getByteSize()) { | |
| printMemoryPostfix(curr->bytes, curr->type); | |
| if (curr->type != Type::f32) { | |
| o << (curr->signed_ ? "_s" : "_u"); | |
| } | |
| } | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| printMemoryOrder(curr->order); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->bytes) { | |
| o << " align=" << curr->align; | |
| } | |
| } | |
| void visitStore(Store* curr) { | |
| prepareColor(o) << forceConcrete(curr->valueType); | |
| if (curr->isAtomic()) { | |
| o << ".atomic"; | |
| } | |
| o << ".store"; | |
| printStorePostfix(curr->bytes, curr->valueType); | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| printMemoryOrder(curr->order); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->bytes) { | |
| o << " align=" << curr->align; | |
| } | |
| } | |
| static void printRMWSize(std::ostream& o, Type type, uint8_t bytes) { | |
| prepareColor(o) << forceConcrete(type) << ".atomic.rmw"; | |
| if (type != Type::unreachable && bytes != type.getByteSize()) { | |
| if (bytes == 1) { | |
| o << '8'; | |
| } else if (bytes == 2) { | |
| o << "16"; | |
| } else if (bytes == 4) { | |
| o << "32"; | |
| } else { | |
| WASM_UNREACHABLE("invalid RMW byte length"); | |
| } | |
| } | |
| o << '.'; | |
| } | |
| void printAtomicRMWOp(AtomicRMWOp op) { | |
| switch (op) { | |
| case RMWAdd: | |
| o << "add"; | |
| return; | |
| case RMWSub: | |
| o << "sub"; | |
| return; | |
| case RMWAnd: | |
| o << "and"; | |
| return; | |
| case RMWOr: | |
| o << "or"; | |
| return; | |
| case RMWXor: | |
| o << "xor"; | |
| return; | |
| case RMWXchg: | |
| o << "xchg"; | |
| return; | |
| } | |
| WASM_UNREACHABLE("unexpected rmw op"); | |
| } | |
| void visitAtomicRMW(AtomicRMW* curr) { | |
| prepareColor(o); | |
| printRMWSize(o, curr->type, curr->bytes); | |
| printAtomicRMWOp(curr->op); | |
| if (curr->type != Type::unreachable && | |
| curr->bytes != curr->type.getByteSize()) { | |
| o << "_u"; | |
| } | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| printMemoryOrder(curr->order); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| } | |
| void visitAtomicCmpxchg(AtomicCmpxchg* curr) { | |
| prepareColor(o); | |
| printRMWSize(o, curr->type, curr->bytes); | |
| o << "cmpxchg"; | |
| if (curr->type != Type::unreachable && | |
| curr->bytes != curr->type.getByteSize()) { | |
| o << "_u"; | |
| } | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| printMemoryOrder(curr->order); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| } | |
| void visitAtomicWait(AtomicWait* curr) { | |
| prepareColor(o); | |
| Type type = forceConcrete(curr->expectedType); | |
| assert(type == Type::i32 || type == Type::i64); | |
| o << "memory.atomic.wait" << (type == Type::i32 ? "32" : "64"); | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| } | |
| void visitAtomicNotify(AtomicNotify* curr) { | |
| printMedium(o, "memory.atomic.notify"); | |
| printMemoryName(curr->memory, o, wasm); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| } | |
| void visitAtomicFence(AtomicFence* curr) { | |
| printMedium(o, "atomic.fence"); | |
| printMemoryOrder(curr->order); | |
| } | |
| void visitPause(Pause* curr) { printMedium(o, "pause"); } | |
| void visitSIMDExtract(SIMDExtract* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case ExtractLaneSVecI8x16: | |
| o << "i8x16.extract_lane_s"; | |
| break; | |
| case ExtractLaneUVecI8x16: | |
| o << "i8x16.extract_lane_u"; | |
| break; | |
| case ExtractLaneSVecI16x8: | |
| o << "i16x8.extract_lane_s"; | |
| break; | |
| case ExtractLaneUVecI16x8: | |
| o << "i16x8.extract_lane_u"; | |
| break; | |
| case ExtractLaneVecI32x4: | |
| o << "i32x4.extract_lane"; | |
| break; | |
| case ExtractLaneVecI64x2: | |
| o << "i64x2.extract_lane"; | |
| break; | |
| case ExtractLaneVecF16x8: | |
| o << "f16x8.extract_lane"; | |
| break; | |
| case ExtractLaneVecF32x4: | |
| o << "f32x4.extract_lane"; | |
| break; | |
| case ExtractLaneVecF64x2: | |
| o << "f64x2.extract_lane"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| o << " " << int(curr->index); | |
| } | |
| void visitSIMDReplace(SIMDReplace* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case ReplaceLaneVecI8x16: | |
| o << "i8x16.replace_lane"; | |
| break; | |
| case ReplaceLaneVecI16x8: | |
| o << "i16x8.replace_lane"; | |
| break; | |
| case ReplaceLaneVecI32x4: | |
| o << "i32x4.replace_lane"; | |
| break; | |
| case ReplaceLaneVecI64x2: | |
| o << "i64x2.replace_lane"; | |
| break; | |
| case ReplaceLaneVecF16x8: | |
| o << "f16x8.replace_lane"; | |
| break; | |
| case ReplaceLaneVecF32x4: | |
| o << "f32x4.replace_lane"; | |
| break; | |
| case ReplaceLaneVecF64x2: | |
| o << "f64x2.replace_lane"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| o << " " << int(curr->index); | |
| } | |
| void visitSIMDShuffle(SIMDShuffle* curr) { | |
| prepareColor(o); | |
| o << "i8x16.shuffle"; | |
| restoreNormalColor(o); | |
| for (uint8_t mask_index : curr->mask) { | |
| o << " " << std::to_string(mask_index); | |
| } | |
| } | |
| void visitSIMDTernary(SIMDTernary* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case Bitselect: | |
| o << "v128.bitselect"; | |
| break; | |
| case RelaxedLaneselectI8x16: | |
| o << "i8x16.relaxed_laneselect"; | |
| break; | |
| case RelaxedLaneselectI16x8: | |
| o << "i16x8.relaxed_laneselect"; | |
| break; | |
| case RelaxedLaneselectI32x4: | |
| o << "i32x4.relaxed_laneselect"; | |
| break; | |
| case RelaxedLaneselectI64x2: | |
| o << "i64x2.relaxed_laneselect"; | |
| break; | |
| case MaddVecF16x8: | |
| o << "f16x8.madd"; | |
| break; | |
| case NmaddVecF16x8: | |
| o << "f16x8.nmadd"; | |
| break; | |
| case RelaxedMaddVecF32x4: | |
| o << "f32x4.relaxed_madd"; | |
| break; | |
| case RelaxedNmaddVecF32x4: | |
| o << "f32x4.relaxed_nmadd"; | |
| break; | |
| case RelaxedMaddVecF64x2: | |
| o << "f64x2.relaxed_madd"; | |
| break; | |
| case RelaxedNmaddVecF64x2: | |
| o << "f64x2.relaxed_nmadd"; | |
| break; | |
| case RelaxedDotI8x16I7x16AddSToVecI32x4: | |
| o << "i32x4.relaxed_dot_i8x16_i7x16_add_s"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitSIMDShift(SIMDShift* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case ShlVecI8x16: | |
| o << "i8x16.shl"; | |
| break; | |
| case ShrSVecI8x16: | |
| o << "i8x16.shr_s"; | |
| break; | |
| case ShrUVecI8x16: | |
| o << "i8x16.shr_u"; | |
| break; | |
| case ShlVecI16x8: | |
| o << "i16x8.shl"; | |
| break; | |
| case ShrSVecI16x8: | |
| o << "i16x8.shr_s"; | |
| break; | |
| case ShrUVecI16x8: | |
| o << "i16x8.shr_u"; | |
| break; | |
| case ShlVecI32x4: | |
| o << "i32x4.shl"; | |
| break; | |
| case ShrSVecI32x4: | |
| o << "i32x4.shr_s"; | |
| break; | |
| case ShrUVecI32x4: | |
| o << "i32x4.shr_u"; | |
| break; | |
| case ShlVecI64x2: | |
| o << "i64x2.shl"; | |
| break; | |
| case ShrSVecI64x2: | |
| o << "i64x2.shr_s"; | |
| break; | |
| case ShrUVecI64x2: | |
| o << "i64x2.shr_u"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitSIMDLoad(SIMDLoad* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case Load8SplatVec128: | |
| o << "v128.load8_splat"; | |
| break; | |
| case Load16SplatVec128: | |
| o << "v128.load16_splat"; | |
| break; | |
| case Load32SplatVec128: | |
| o << "v128.load32_splat"; | |
| break; | |
| case Load64SplatVec128: | |
| o << "v128.load64_splat"; | |
| break; | |
| case Load8x8SVec128: | |
| o << "v128.load8x8_s"; | |
| break; | |
| case Load8x8UVec128: | |
| o << "v128.load8x8_u"; | |
| break; | |
| case Load16x4SVec128: | |
| o << "v128.load16x4_s"; | |
| break; | |
| case Load16x4UVec128: | |
| o << "v128.load16x4_u"; | |
| break; | |
| case Load32x2SVec128: | |
| o << "v128.load32x2_s"; | |
| break; | |
| case Load32x2UVec128: | |
| o << "v128.load32x2_u"; | |
| break; | |
| case Load32ZeroVec128: | |
| o << "v128.load32_zero"; | |
| break; | |
| case Load64ZeroVec128: | |
| o << "v128.load64_zero"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->getMemBytes()) { | |
| o << " align=" << curr->align; | |
| } | |
| } | |
| void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case Load8LaneVec128: | |
| o << "v128.load8_lane"; | |
| break; | |
| case Load16LaneVec128: | |
| o << "v128.load16_lane"; | |
| break; | |
| case Load32LaneVec128: | |
| o << "v128.load32_lane"; | |
| break; | |
| case Load64LaneVec128: | |
| o << "v128.load64_lane"; | |
| break; | |
| case Store8LaneVec128: | |
| o << "v128.store8_lane"; | |
| break; | |
| case Store16LaneVec128: | |
| o << "v128.store16_lane"; | |
| break; | |
| case Store32LaneVec128: | |
| o << "v128.store32_lane"; | |
| break; | |
| case Store64LaneVec128: | |
| o << "v128.store64_lane"; | |
| break; | |
| } | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->getMemBytes()) { | |
| o << " align=" << curr->align; | |
| } | |
| o << " " << int(curr->index); | |
| } | |
| void visitMemoryInit(MemoryInit* curr) { | |
| prepareColor(o); | |
| o << "memory.init"; | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitDataDrop(DataDrop* curr) { | |
| prepareColor(o); | |
| o << "data.drop"; | |
| restoreNormalColor(o); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitMemoryCopy(MemoryCopy* curr) { | |
| prepareColor(o); | |
| o << "memory.copy"; | |
| restoreNormalColor(o); | |
| printMemoryName(curr->destMemory, o, wasm); | |
| printMemoryName(curr->sourceMemory, o, wasm); | |
| } | |
| void visitMemoryFill(MemoryFill* curr) { | |
| prepareColor(o); | |
| o << "memory.fill"; | |
| restoreNormalColor(o); | |
| printMemoryName(curr->memory, o, wasm); | |
| } | |
| void visitConst(Const* curr) { | |
| o << curr->value.type << ".const " << curr->value; | |
| } | |
| void visitUnary(Unary* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case ClzInt32: | |
| o << "i32.clz"; | |
| break; | |
| case CtzInt32: | |
| o << "i32.ctz"; | |
| break; | |
| case PopcntInt32: | |
| o << "i32.popcnt"; | |
| break; | |
| case EqZInt32: | |
| o << "i32.eqz"; | |
| break; | |
| case ClzInt64: | |
| o << "i64.clz"; | |
| break; | |
| case CtzInt64: | |
| o << "i64.ctz"; | |
| break; | |
| case PopcntInt64: | |
| o << "i64.popcnt"; | |
| break; | |
| case EqZInt64: | |
| o << "i64.eqz"; | |
| break; | |
| case NegFloat32: | |
| o << "f32.neg"; | |
| break; | |
| case AbsFloat32: | |
| o << "f32.abs"; | |
| break; | |
| case CeilFloat32: | |
| o << "f32.ceil"; | |
| break; | |
| case FloorFloat32: | |
| o << "f32.floor"; | |
| break; | |
| case TruncFloat32: | |
| o << "f32.trunc"; | |
| break; | |
| case NearestFloat32: | |
| o << "f32.nearest"; | |
| break; | |
| case SqrtFloat32: | |
| o << "f32.sqrt"; | |
| break; | |
| case NegFloat64: | |
| o << "f64.neg"; | |
| break; | |
| case AbsFloat64: | |
| o << "f64.abs"; | |
| break; | |
| case CeilFloat64: | |
| o << "f64.ceil"; | |
| break; | |
| case FloorFloat64: | |
| o << "f64.floor"; | |
| break; | |
| case TruncFloat64: | |
| o << "f64.trunc"; | |
| break; | |
| case NearestFloat64: | |
| o << "f64.nearest"; | |
| break; | |
| case SqrtFloat64: | |
| o << "f64.sqrt"; | |
| break; | |
| case ExtendSInt32: | |
| o << "i64.extend_i32_s"; | |
| break; | |
| case ExtendUInt32: | |
| o << "i64.extend_i32_u"; | |
| break; | |
| case WrapInt64: | |
| o << "i32.wrap_i64"; | |
| break; | |
| case TruncSFloat32ToInt32: | |
| o << "i32.trunc_f32_s"; | |
| break; | |
| case TruncSFloat32ToInt64: | |
| o << "i64.trunc_f32_s"; | |
| break; | |
| case TruncUFloat32ToInt32: | |
| o << "i32.trunc_f32_u"; | |
| break; | |
| case TruncUFloat32ToInt64: | |
| o << "i64.trunc_f32_u"; | |
| break; | |
| case TruncSFloat64ToInt32: | |
| o << "i32.trunc_f64_s"; | |
| break; | |
| case TruncSFloat64ToInt64: | |
| o << "i64.trunc_f64_s"; | |
| break; | |
| case TruncUFloat64ToInt32: | |
| o << "i32.trunc_f64_u"; | |
| break; | |
| case TruncUFloat64ToInt64: | |
| o << "i64.trunc_f64_u"; | |
| break; | |
| case ReinterpretFloat32: | |
| o << "i32.reinterpret_f32"; | |
| break; | |
| case ReinterpretFloat64: | |
| o << "i64.reinterpret_f64"; | |
| break; | |
| case ConvertUInt32ToFloat32: | |
| o << "f32.convert_i32_u"; | |
| break; | |
| case ConvertUInt32ToFloat64: | |
| o << "f64.convert_i32_u"; | |
| break; | |
| case ConvertSInt32ToFloat32: | |
| o << "f32.convert_i32_s"; | |
| break; | |
| case ConvertSInt32ToFloat64: | |
| o << "f64.convert_i32_s"; | |
| break; | |
| case ConvertUInt64ToFloat32: | |
| o << "f32.convert_i64_u"; | |
| break; | |
| case ConvertUInt64ToFloat64: | |
| o << "f64.convert_i64_u"; | |
| break; | |
| case ConvertSInt64ToFloat32: | |
| o << "f32.convert_i64_s"; | |
| break; | |
| case ConvertSInt64ToFloat64: | |
| o << "f64.convert_i64_s"; | |
| break; | |
| case PromoteFloat32: | |
| o << "f64.promote_f32"; | |
| break; | |
| case DemoteFloat64: | |
| o << "f32.demote_f64"; | |
| break; | |
| case ReinterpretInt32: | |
| o << "f32.reinterpret_i32"; | |
| break; | |
| case ReinterpretInt64: | |
| o << "f64.reinterpret_i64"; | |
| break; | |
| case ExtendS8Int32: | |
| o << "i32.extend8_s"; | |
| break; | |
| case ExtendS16Int32: | |
| o << "i32.extend16_s"; | |
| break; | |
| case ExtendS8Int64: | |
| o << "i64.extend8_s"; | |
| break; | |
| case ExtendS16Int64: | |
| o << "i64.extend16_s"; | |
| break; | |
| case ExtendS32Int64: | |
| o << "i64.extend32_s"; | |
| break; | |
| case TruncSatSFloat32ToInt32: | |
| o << "i32.trunc_sat_f32_s"; | |
| break; | |
| case TruncSatUFloat32ToInt32: | |
| o << "i32.trunc_sat_f32_u"; | |
| break; | |
| case TruncSatSFloat64ToInt32: | |
| o << "i32.trunc_sat_f64_s"; | |
| break; | |
| case TruncSatUFloat64ToInt32: | |
| o << "i32.trunc_sat_f64_u"; | |
| break; | |
| case TruncSatSFloat32ToInt64: | |
| o << "i64.trunc_sat_f32_s"; | |
| break; | |
| case TruncSatUFloat32ToInt64: | |
| o << "i64.trunc_sat_f32_u"; | |
| break; | |
| case TruncSatSFloat64ToInt64: | |
| o << "i64.trunc_sat_f64_s"; | |
| break; | |
| case TruncSatUFloat64ToInt64: | |
| o << "i64.trunc_sat_f64_u"; | |
| break; | |
| case SplatVecI8x16: | |
| o << "i8x16.splat"; | |
| break; | |
| case SplatVecI16x8: | |
| o << "i16x8.splat"; | |
| break; | |
| case SplatVecI32x4: | |
| o << "i32x4.splat"; | |
| break; | |
| case SplatVecI64x2: | |
| o << "i64x2.splat"; | |
| break; | |
| case SplatVecF16x8: | |
| o << "f16x8.splat"; | |
| break; | |
| case SplatVecF32x4: | |
| o << "f32x4.splat"; | |
| break; | |
| case SplatVecF64x2: | |
| o << "f64x2.splat"; | |
| break; | |
| case NotVec128: | |
| o << "v128.not"; | |
| break; | |
| case AnyTrueVec128: | |
| o << "v128.any_true"; | |
| break; | |
| case AbsVecI8x16: | |
| o << "i8x16.abs"; | |
| break; | |
| case NegVecI8x16: | |
| o << "i8x16.neg"; | |
| break; | |
| case AllTrueVecI8x16: | |
| o << "i8x16.all_true"; | |
| break; | |
| case BitmaskVecI8x16: | |
| o << "i8x16.bitmask"; | |
| break; | |
| case PopcntVecI8x16: | |
| o << "i8x16.popcnt"; | |
| break; | |
| case AbsVecI16x8: | |
| o << "i16x8.abs"; | |
| break; | |
| case NegVecI16x8: | |
| o << "i16x8.neg"; | |
| break; | |
| case AllTrueVecI16x8: | |
| o << "i16x8.all_true"; | |
| break; | |
| case BitmaskVecI16x8: | |
| o << "i16x8.bitmask"; | |
| break; | |
| case AbsVecI32x4: | |
| o << "i32x4.abs"; | |
| break; | |
| case NegVecI32x4: | |
| o << "i32x4.neg"; | |
| break; | |
| case AllTrueVecI32x4: | |
| o << "i32x4.all_true"; | |
| break; | |
| case BitmaskVecI32x4: | |
| o << "i32x4.bitmask"; | |
| break; | |
| case AbsVecI64x2: | |
| o << "i64x2.abs"; | |
| break; | |
| case NegVecI64x2: | |
| o << "i64x2.neg"; | |
| break; | |
| case AllTrueVecI64x2: | |
| o << "i64x2.all_true"; | |
| break; | |
| case BitmaskVecI64x2: | |
| o << "i64x2.bitmask"; | |
| break; | |
| case AbsVecF16x8: | |
| o << "f16x8.abs"; | |
| break; | |
| case NegVecF16x8: | |
| o << "f16x8.neg"; | |
| break; | |
| case SqrtVecF16x8: | |
| o << "f16x8.sqrt"; | |
| break; | |
| case CeilVecF16x8: | |
| o << "f16x8.ceil"; | |
| break; | |
| case FloorVecF16x8: | |
| o << "f16x8.floor"; | |
| break; | |
| case TruncVecF16x8: | |
| o << "f16x8.trunc"; | |
| break; | |
| case NearestVecF16x8: | |
| o << "f16x8.nearest"; | |
| break; | |
| case AbsVecF32x4: | |
| o << "f32x4.abs"; | |
| break; | |
| case NegVecF32x4: | |
| o << "f32x4.neg"; | |
| break; | |
| case SqrtVecF32x4: | |
| o << "f32x4.sqrt"; | |
| break; | |
| case CeilVecF32x4: | |
| o << "f32x4.ceil"; | |
| break; | |
| case FloorVecF32x4: | |
| o << "f32x4.floor"; | |
| break; | |
| case TruncVecF32x4: | |
| o << "f32x4.trunc"; | |
| break; | |
| case NearestVecF32x4: | |
| o << "f32x4.nearest"; | |
| break; | |
| case AbsVecF64x2: | |
| o << "f64x2.abs"; | |
| break; | |
| case NegVecF64x2: | |
| o << "f64x2.neg"; | |
| break; | |
| case SqrtVecF64x2: | |
| o << "f64x2.sqrt"; | |
| break; | |
| case CeilVecF64x2: | |
| o << "f64x2.ceil"; | |
| break; | |
| case FloorVecF64x2: | |
| o << "f64x2.floor"; | |
| break; | |
| case TruncVecF64x2: | |
| o << "f64x2.trunc"; | |
| break; | |
| case NearestVecF64x2: | |
| o << "f64x2.nearest"; | |
| break; | |
| case ExtAddPairwiseSVecI8x16ToI16x8: | |
| o << "i16x8.extadd_pairwise_i8x16_s"; | |
| break; | |
| case ExtAddPairwiseUVecI8x16ToI16x8: | |
| o << "i16x8.extadd_pairwise_i8x16_u"; | |
| break; | |
| case ExtAddPairwiseSVecI16x8ToI32x4: | |
| o << "i32x4.extadd_pairwise_i16x8_s"; | |
| break; | |
| case ExtAddPairwiseUVecI16x8ToI32x4: | |
| o << "i32x4.extadd_pairwise_i16x8_u"; | |
| break; | |
| case TruncSatSVecF32x4ToVecI32x4: | |
| o << "i32x4.trunc_sat_f32x4_s"; | |
| break; | |
| case TruncSatUVecF32x4ToVecI32x4: | |
| o << "i32x4.trunc_sat_f32x4_u"; | |
| break; | |
| case ConvertSVecI32x4ToVecF32x4: | |
| o << "f32x4.convert_i32x4_s"; | |
| break; | |
| case ConvertUVecI32x4ToVecF32x4: | |
| o << "f32x4.convert_i32x4_u"; | |
| break; | |
| case ExtendLowSVecI8x16ToVecI16x8: | |
| o << "i16x8.extend_low_i8x16_s"; | |
| break; | |
| case ExtendHighSVecI8x16ToVecI16x8: | |
| o << "i16x8.extend_high_i8x16_s"; | |
| break; | |
| case ExtendLowUVecI8x16ToVecI16x8: | |
| o << "i16x8.extend_low_i8x16_u"; | |
| break; | |
| case ExtendHighUVecI8x16ToVecI16x8: | |
| o << "i16x8.extend_high_i8x16_u"; | |
| break; | |
| case ExtendLowSVecI16x8ToVecI32x4: | |
| o << "i32x4.extend_low_i16x8_s"; | |
| break; | |
| case ExtendHighSVecI16x8ToVecI32x4: | |
| o << "i32x4.extend_high_i16x8_s"; | |
| break; | |
| case ExtendLowUVecI16x8ToVecI32x4: | |
| o << "i32x4.extend_low_i16x8_u"; | |
| break; | |
| case ExtendHighUVecI16x8ToVecI32x4: | |
| o << "i32x4.extend_high_i16x8_u"; | |
| break; | |
| case ExtendLowSVecI32x4ToVecI64x2: | |
| o << "i64x2.extend_low_i32x4_s"; | |
| break; | |
| case ExtendHighSVecI32x4ToVecI64x2: | |
| o << "i64x2.extend_high_i32x4_s"; | |
| break; | |
| case ExtendLowUVecI32x4ToVecI64x2: | |
| o << "i64x2.extend_low_i32x4_u"; | |
| break; | |
| case ExtendHighUVecI32x4ToVecI64x2: | |
| o << "i64x2.extend_high_i32x4_u"; | |
| break; | |
| case ConvertLowSVecI32x4ToVecF64x2: | |
| o << "f64x2.convert_low_i32x4_s"; | |
| break; | |
| case ConvertLowUVecI32x4ToVecF64x2: | |
| o << "f64x2.convert_low_i32x4_u"; | |
| break; | |
| case TruncSatZeroSVecF64x2ToVecI32x4: | |
| o << "i32x4.trunc_sat_f64x2_s_zero"; | |
| break; | |
| case TruncSatZeroUVecF64x2ToVecI32x4: | |
| o << "i32x4.trunc_sat_f64x2_u_zero"; | |
| break; | |
| case DemoteZeroVecF64x2ToVecF32x4: | |
| o << "f32x4.demote_f64x2_zero"; | |
| break; | |
| case PromoteLowVecF32x4ToVecF64x2: | |
| o << "f64x2.promote_low_f32x4"; | |
| break; | |
| case RelaxedTruncSVecF32x4ToVecI32x4: | |
| o << "i32x4.relaxed_trunc_f32x4_s"; | |
| break; | |
| case RelaxedTruncUVecF32x4ToVecI32x4: | |
| o << "i32x4.relaxed_trunc_f32x4_u"; | |
| break; | |
| case RelaxedTruncZeroSVecF64x2ToVecI32x4: | |
| o << "i32x4.relaxed_trunc_f64x2_s_zero"; | |
| break; | |
| case RelaxedTruncZeroUVecF64x2ToVecI32x4: | |
| o << "i32x4.relaxed_trunc_f64x2_u_zero"; | |
| break; | |
| case TruncSatSVecF16x8ToVecI16x8: | |
| o << "i16x8.trunc_sat_f16x8_s"; | |
| break; | |
| case TruncSatUVecF16x8ToVecI16x8: | |
| o << "i16x8.trunc_sat_f16x8_u"; | |
| break; | |
| case ConvertSVecI16x8ToVecF16x8: | |
| o << "f16x8.convert_i16x8_s"; | |
| break; | |
| case ConvertUVecI16x8ToVecF16x8: | |
| o << "f16x8.convert_i16x8_u"; | |
| break; | |
| case PromoteLowVecF16x8ToVecF32x4: | |
| o << "f32x4.promote_low_f16x8"; | |
| break; | |
| case DemoteZeroVecF32x4ToVecF16x8: | |
| o << "f16x8.demote_f32x4_zero"; | |
| break; | |
| case DemoteZeroVecF64x2ToVecF16x8: | |
| o << "f16x8.demote_f64x2_zero"; | |
| break; | |
| case InvalidUnary: | |
| WASM_UNREACHABLE("invalid unary operator"); | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitBinary(Binary* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case AddInt32: | |
| o << "i32.add"; | |
| break; | |
| case SubInt32: | |
| o << "i32.sub"; | |
| break; | |
| case MulInt32: | |
| o << "i32.mul"; | |
| break; | |
| case DivSInt32: | |
| o << "i32.div_s"; | |
| break; | |
| case DivUInt32: | |
| o << "i32.div_u"; | |
| break; | |
| case RemSInt32: | |
| o << "i32.rem_s"; | |
| break; | |
| case RemUInt32: | |
| o << "i32.rem_u"; | |
| break; | |
| case AndInt32: | |
| o << "i32.and"; | |
| break; | |
| case OrInt32: | |
| o << "i32.or"; | |
| break; | |
| case XorInt32: | |
| o << "i32.xor"; | |
| break; | |
| case ShlInt32: | |
| o << "i32.shl"; | |
| break; | |
| case ShrUInt32: | |
| o << "i32.shr_u"; | |
| break; | |
| case ShrSInt32: | |
| o << "i32.shr_s"; | |
| break; | |
| case RotLInt32: | |
| o << "i32.rotl"; | |
| break; | |
| case RotRInt32: | |
| o << "i32.rotr"; | |
| break; | |
| case EqInt32: | |
| o << "i32.eq"; | |
| break; | |
| case NeInt32: | |
| o << "i32.ne"; | |
| break; | |
| case LtSInt32: | |
| o << "i32.lt_s"; | |
| break; | |
| case LtUInt32: | |
| o << "i32.lt_u"; | |
| break; | |
| case LeSInt32: | |
| o << "i32.le_s"; | |
| break; | |
| case LeUInt32: | |
| o << "i32.le_u"; | |
| break; | |
| case GtSInt32: | |
| o << "i32.gt_s"; | |
| break; | |
| case GtUInt32: | |
| o << "i32.gt_u"; | |
| break; | |
| case GeSInt32: | |
| o << "i32.ge_s"; | |
| break; | |
| case GeUInt32: | |
| o << "i32.ge_u"; | |
| break; | |
| case AddInt64: | |
| o << "i64.add"; | |
| break; | |
| case SubInt64: | |
| o << "i64.sub"; | |
| break; | |
| case MulInt64: | |
| o << "i64.mul"; | |
| break; | |
| case DivSInt64: | |
| o << "i64.div_s"; | |
| break; | |
| case DivUInt64: | |
| o << "i64.div_u"; | |
| break; | |
| case RemSInt64: | |
| o << "i64.rem_s"; | |
| break; | |
| case RemUInt64: | |
| o << "i64.rem_u"; | |
| break; | |
| case AndInt64: | |
| o << "i64.and"; | |
| break; | |
| case OrInt64: | |
| o << "i64.or"; | |
| break; | |
| case XorInt64: | |
| o << "i64.xor"; | |
| break; | |
| case ShlInt64: | |
| o << "i64.shl"; | |
| break; | |
| case ShrUInt64: | |
| o << "i64.shr_u"; | |
| break; | |
| case ShrSInt64: | |
| o << "i64.shr_s"; | |
| break; | |
| case RotLInt64: | |
| o << "i64.rotl"; | |
| break; | |
| case RotRInt64: | |
| o << "i64.rotr"; | |
| break; | |
| case EqInt64: | |
| o << "i64.eq"; | |
| break; | |
| case NeInt64: | |
| o << "i64.ne"; | |
| break; | |
| case LtSInt64: | |
| o << "i64.lt_s"; | |
| break; | |
| case LtUInt64: | |
| o << "i64.lt_u"; | |
| break; | |
| case LeSInt64: | |
| o << "i64.le_s"; | |
| break; | |
| case LeUInt64: | |
| o << "i64.le_u"; | |
| break; | |
| case GtSInt64: | |
| o << "i64.gt_s"; | |
| break; | |
| case GtUInt64: | |
| o << "i64.gt_u"; | |
| break; | |
| case GeSInt64: | |
| o << "i64.ge_s"; | |
| break; | |
| case GeUInt64: | |
| o << "i64.ge_u"; | |
| break; | |
| case AddFloat32: | |
| o << "f32.add"; | |
| break; | |
| case SubFloat32: | |
| o << "f32.sub"; | |
| break; | |
| case MulFloat32: | |
| o << "f32.mul"; | |
| break; | |
| case DivFloat32: | |
| o << "f32.div"; | |
| break; | |
| case CopySignFloat32: | |
| o << "f32.copysign"; | |
| break; | |
| case MinFloat32: | |
| o << "f32.min"; | |
| break; | |
| case MaxFloat32: | |
| o << "f32.max"; | |
| break; | |
| case EqFloat32: | |
| o << "f32.eq"; | |
| break; | |
| case NeFloat32: | |
| o << "f32.ne"; | |
| break; | |
| case LtFloat32: | |
| o << "f32.lt"; | |
| break; | |
| case LeFloat32: | |
| o << "f32.le"; | |
| break; | |
| case GtFloat32: | |
| o << "f32.gt"; | |
| break; | |
| case GeFloat32: | |
| o << "f32.ge"; | |
| break; | |
| case AddFloat64: | |
| o << "f64.add"; | |
| break; | |
| case SubFloat64: | |
| o << "f64.sub"; | |
| break; | |
| case MulFloat64: | |
| o << "f64.mul"; | |
| break; | |
| case DivFloat64: | |
| o << "f64.div"; | |
| break; | |
| case CopySignFloat64: | |
| o << "f64.copysign"; | |
| break; | |
| case MinFloat64: | |
| o << "f64.min"; | |
| break; | |
| case MaxFloat64: | |
| o << "f64.max"; | |
| break; | |
| case EqFloat64: | |
| o << "f64.eq"; | |
| break; | |
| case NeFloat64: | |
| o << "f64.ne"; | |
| break; | |
| case LtFloat64: | |
| o << "f64.lt"; | |
| break; | |
| case LeFloat64: | |
| o << "f64.le"; | |
| break; | |
| case GtFloat64: | |
| o << "f64.gt"; | |
| break; | |
| case GeFloat64: | |
| o << "f64.ge"; | |
| break; | |
| case EqVecI8x16: | |
| o << "i8x16.eq"; | |
| break; | |
| case NeVecI8x16: | |
| o << "i8x16.ne"; | |
| break; | |
| case LtSVecI8x16: | |
| o << "i8x16.lt_s"; | |
| break; | |
| case LtUVecI8x16: | |
| o << "i8x16.lt_u"; | |
| break; | |
| case GtSVecI8x16: | |
| o << "i8x16.gt_s"; | |
| break; | |
| case GtUVecI8x16: | |
| o << "i8x16.gt_u"; | |
| break; | |
| case LeSVecI8x16: | |
| o << "i8x16.le_s"; | |
| break; | |
| case LeUVecI8x16: | |
| o << "i8x16.le_u"; | |
| break; | |
| case GeSVecI8x16: | |
| o << "i8x16.ge_s"; | |
| break; | |
| case GeUVecI8x16: | |
| o << "i8x16.ge_u"; | |
| break; | |
| case EqVecI16x8: | |
| o << "i16x8.eq"; | |
| break; | |
| case NeVecI16x8: | |
| o << "i16x8.ne"; | |
| break; | |
| case LtSVecI16x8: | |
| o << "i16x8.lt_s"; | |
| break; | |
| case LtUVecI16x8: | |
| o << "i16x8.lt_u"; | |
| break; | |
| case GtSVecI16x8: | |
| o << "i16x8.gt_s"; | |
| break; | |
| case GtUVecI16x8: | |
| o << "i16x8.gt_u"; | |
| break; | |
| case LeSVecI16x8: | |
| o << "i16x8.le_s"; | |
| break; | |
| case LeUVecI16x8: | |
| o << "i16x8.le_u"; | |
| break; | |
| case GeSVecI16x8: | |
| o << "i16x8.ge_s"; | |
| break; | |
| case GeUVecI16x8: | |
| o << "i16x8.ge_u"; | |
| break; | |
| case EqVecI32x4: | |
| o << "i32x4.eq"; | |
| break; | |
| case NeVecI32x4: | |
| o << "i32x4.ne"; | |
| break; | |
| case LtSVecI32x4: | |
| o << "i32x4.lt_s"; | |
| break; | |
| case LtUVecI32x4: | |
| o << "i32x4.lt_u"; | |
| break; | |
| case GtSVecI32x4: | |
| o << "i32x4.gt_s"; | |
| break; | |
| case GtUVecI32x4: | |
| o << "i32x4.gt_u"; | |
| break; | |
| case LeSVecI32x4: | |
| o << "i32x4.le_s"; | |
| break; | |
| case LeUVecI32x4: | |
| o << "i32x4.le_u"; | |
| break; | |
| case GeSVecI32x4: | |
| o << "i32x4.ge_s"; | |
| break; | |
| case GeUVecI32x4: | |
| o << "i32x4.ge_u"; | |
| break; | |
| case EqVecI64x2: | |
| o << "i64x2.eq"; | |
| break; | |
| case NeVecI64x2: | |
| o << "i64x2.ne"; | |
| break; | |
| case LtSVecI64x2: | |
| o << "i64x2.lt_s"; | |
| break; | |
| case GtSVecI64x2: | |
| o << "i64x2.gt_s"; | |
| break; | |
| case LeSVecI64x2: | |
| o << "i64x2.le_s"; | |
| break; | |
| case GeSVecI64x2: | |
| o << "i64x2.ge_s"; | |
| break; | |
| case EqVecF16x8: | |
| o << "f16x8.eq"; | |
| break; | |
| case NeVecF16x8: | |
| o << "f16x8.ne"; | |
| break; | |
| case LtVecF16x8: | |
| o << "f16x8.lt"; | |
| break; | |
| case GtVecF16x8: | |
| o << "f16x8.gt"; | |
| break; | |
| case LeVecF16x8: | |
| o << "f16x8.le"; | |
| break; | |
| case GeVecF16x8: | |
| o << "f16x8.ge"; | |
| break; | |
| case EqVecF32x4: | |
| o << "f32x4.eq"; | |
| break; | |
| case NeVecF32x4: | |
| o << "f32x4.ne"; | |
| break; | |
| case LtVecF32x4: | |
| o << "f32x4.lt"; | |
| break; | |
| case GtVecF32x4: | |
| o << "f32x4.gt"; | |
| break; | |
| case LeVecF32x4: | |
| o << "f32x4.le"; | |
| break; | |
| case GeVecF32x4: | |
| o << "f32x4.ge"; | |
| break; | |
| case EqVecF64x2: | |
| o << "f64x2.eq"; | |
| break; | |
| case NeVecF64x2: | |
| o << "f64x2.ne"; | |
| break; | |
| case LtVecF64x2: | |
| o << "f64x2.lt"; | |
| break; | |
| case GtVecF64x2: | |
| o << "f64x2.gt"; | |
| break; | |
| case LeVecF64x2: | |
| o << "f64x2.le"; | |
| break; | |
| case GeVecF64x2: | |
| o << "f64x2.ge"; | |
| break; | |
| case AndVec128: | |
| o << "v128.and"; | |
| break; | |
| case OrVec128: | |
| o << "v128.or"; | |
| break; | |
| case XorVec128: | |
| o << "v128.xor"; | |
| break; | |
| case AndNotVec128: | |
| o << "v128.andnot"; | |
| break; | |
| case AddVecI8x16: | |
| o << "i8x16.add"; | |
| break; | |
| case AddSatSVecI8x16: | |
| o << "i8x16.add_sat_s"; | |
| break; | |
| case AddSatUVecI8x16: | |
| o << "i8x16.add_sat_u"; | |
| break; | |
| case SubVecI8x16: | |
| o << "i8x16.sub"; | |
| break; | |
| case SubSatSVecI8x16: | |
| o << "i8x16.sub_sat_s"; | |
| break; | |
| case SubSatUVecI8x16: | |
| o << "i8x16.sub_sat_u"; | |
| break; | |
| case MinSVecI8x16: | |
| o << "i8x16.min_s"; | |
| break; | |
| case MinUVecI8x16: | |
| o << "i8x16.min_u"; | |
| break; | |
| case MaxSVecI8x16: | |
| o << "i8x16.max_s"; | |
| break; | |
| case MaxUVecI8x16: | |
| o << "i8x16.max_u"; | |
| break; | |
| case AvgrUVecI8x16: | |
| o << "i8x16.avgr_u"; | |
| break; | |
| case AddVecI16x8: | |
| o << "i16x8.add"; | |
| break; | |
| case AddSatSVecI16x8: | |
| o << "i16x8.add_sat_s"; | |
| break; | |
| case AddSatUVecI16x8: | |
| o << "i16x8.add_sat_u"; | |
| break; | |
| case SubVecI16x8: | |
| o << "i16x8.sub"; | |
| break; | |
| case SubSatSVecI16x8: | |
| o << "i16x8.sub_sat_s"; | |
| break; | |
| case SubSatUVecI16x8: | |
| o << "i16x8.sub_sat_u"; | |
| break; | |
| case MulVecI16x8: | |
| o << "i16x8.mul"; | |
| break; | |
| case MinSVecI16x8: | |
| o << "i16x8.min_s"; | |
| break; | |
| case MinUVecI16x8: | |
| o << "i16x8.min_u"; | |
| break; | |
| case MaxSVecI16x8: | |
| o << "i16x8.max_s"; | |
| break; | |
| case MaxUVecI16x8: | |
| o << "i16x8.max_u"; | |
| break; | |
| case AvgrUVecI16x8: | |
| o << "i16x8.avgr_u"; | |
| break; | |
| case Q15MulrSatSVecI16x8: | |
| o << "i16x8.q15mulr_sat_s"; | |
| break; | |
| case ExtMulLowSVecI16x8: | |
| o << "i16x8.extmul_low_i8x16_s"; | |
| break; | |
| case ExtMulHighSVecI16x8: | |
| o << "i16x8.extmul_high_i8x16_s"; | |
| break; | |
| case ExtMulLowUVecI16x8: | |
| o << "i16x8.extmul_low_i8x16_u"; | |
| break; | |
| case ExtMulHighUVecI16x8: | |
| o << "i16x8.extmul_high_i8x16_u"; | |
| break; | |
| case AddVecI32x4: | |
| o << "i32x4.add"; | |
| break; | |
| case SubVecI32x4: | |
| o << "i32x4.sub"; | |
| break; | |
| case MulVecI32x4: | |
| o << "i32x4.mul"; | |
| break; | |
| case MinSVecI32x4: | |
| o << "i32x4.min_s"; | |
| break; | |
| case MinUVecI32x4: | |
| o << "i32x4.min_u"; | |
| break; | |
| case MaxSVecI32x4: | |
| o << "i32x4.max_s"; | |
| break; | |
| case MaxUVecI32x4: | |
| o << "i32x4.max_u"; | |
| break; | |
| case DotSVecI16x8ToVecI32x4: | |
| o << "i32x4.dot_i16x8_s"; | |
| break; | |
| case ExtMulLowSVecI32x4: | |
| o << "i32x4.extmul_low_i16x8_s"; | |
| break; | |
| case ExtMulHighSVecI32x4: | |
| o << "i32x4.extmul_high_i16x8_s"; | |
| break; | |
| case ExtMulLowUVecI32x4: | |
| o << "i32x4.extmul_low_i16x8_u"; | |
| break; | |
| case ExtMulHighUVecI32x4: | |
| o << "i32x4.extmul_high_i16x8_u"; | |
| break; | |
| case AddVecI64x2: | |
| o << "i64x2.add"; | |
| break; | |
| case SubVecI64x2: | |
| o << "i64x2.sub"; | |
| break; | |
| case MulVecI64x2: | |
| o << "i64x2.mul"; | |
| break; | |
| case ExtMulLowSVecI64x2: | |
| o << "i64x2.extmul_low_i32x4_s"; | |
| break; | |
| case ExtMulHighSVecI64x2: | |
| o << "i64x2.extmul_high_i32x4_s"; | |
| break; | |
| case ExtMulLowUVecI64x2: | |
| o << "i64x2.extmul_low_i32x4_u"; | |
| break; | |
| case ExtMulHighUVecI64x2: | |
| o << "i64x2.extmul_high_i32x4_u"; | |
| break; | |
| case AddVecF16x8: | |
| o << "f16x8.add"; | |
| break; | |
| case SubVecF16x8: | |
| o << "f16x8.sub"; | |
| break; | |
| case MulVecF16x8: | |
| o << "f16x8.mul"; | |
| break; | |
| case DivVecF16x8: | |
| o << "f16x8.div"; | |
| break; | |
| case MinVecF16x8: | |
| o << "f16x8.min"; | |
| break; | |
| case MaxVecF16x8: | |
| o << "f16x8.max"; | |
| break; | |
| case PMinVecF16x8: | |
| o << "f16x8.pmin"; | |
| break; | |
| case PMaxVecF16x8: | |
| o << "f16x8.pmax"; | |
| break; | |
| case AddVecF32x4: | |
| o << "f32x4.add"; | |
| break; | |
| case SubVecF32x4: | |
| o << "f32x4.sub"; | |
| break; | |
| case MulVecF32x4: | |
| o << "f32x4.mul"; | |
| break; | |
| case DivVecF32x4: | |
| o << "f32x4.div"; | |
| break; | |
| case MinVecF32x4: | |
| o << "f32x4.min"; | |
| break; | |
| case MaxVecF32x4: | |
| o << "f32x4.max"; | |
| break; | |
| case PMinVecF32x4: | |
| o << "f32x4.pmin"; | |
| break; | |
| case PMaxVecF32x4: | |
| o << "f32x4.pmax"; | |
| break; | |
| case AddVecF64x2: | |
| o << "f64x2.add"; | |
| break; | |
| case SubVecF64x2: | |
| o << "f64x2.sub"; | |
| break; | |
| case MulVecF64x2: | |
| o << "f64x2.mul"; | |
| break; | |
| case DivVecF64x2: | |
| o << "f64x2.div"; | |
| break; | |
| case MinVecF64x2: | |
| o << "f64x2.min"; | |
| break; | |
| case MaxVecF64x2: | |
| o << "f64x2.max"; | |
| break; | |
| case PMinVecF64x2: | |
| o << "f64x2.pmin"; | |
| break; | |
| case PMaxVecF64x2: | |
| o << "f64x2.pmax"; | |
| break; | |
| case NarrowSVecI16x8ToVecI8x16: | |
| o << "i8x16.narrow_i16x8_s"; | |
| break; | |
| case NarrowUVecI16x8ToVecI8x16: | |
| o << "i8x16.narrow_i16x8_u"; | |
| break; | |
| case NarrowSVecI32x4ToVecI16x8: | |
| o << "i16x8.narrow_i32x4_s"; | |
| break; | |
| case NarrowUVecI32x4ToVecI16x8: | |
| o << "i16x8.narrow_i32x4_u"; | |
| break; | |
| case SwizzleVecI8x16: | |
| o << "i8x16.swizzle"; | |
| break; | |
| case RelaxedMinVecF32x4: | |
| o << "f32x4.relaxed_min"; | |
| break; | |
| case RelaxedMaxVecF32x4: | |
| o << "f32x4.relaxed_max"; | |
| break; | |
| case RelaxedMinVecF64x2: | |
| o << "f64x2.relaxed_min"; | |
| break; | |
| case RelaxedMaxVecF64x2: | |
| o << "f64x2.relaxed_max"; | |
| break; | |
| case RelaxedSwizzleVecI8x16: | |
| o << "i8x16.relaxed_swizzle"; | |
| break; | |
| case RelaxedQ15MulrSVecI16x8: | |
| o << "i16x8.relaxed_q15mulr_s"; | |
| break; | |
| case RelaxedDotI8x16I7x16SToVecI16x8: | |
| o << "i16x8.relaxed_dot_i8x16_i7x16_s"; | |
| break; | |
| case InvalidBinary: | |
| WASM_UNREACHABLE("invalid binary operator"); | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitWideIntAddSub(WideIntAddSub* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case AddInt128: { | |
| o << "i64.add128"; | |
| break; | |
| } | |
| case SubInt128: { | |
| o << "i64.sub128"; | |
| break; | |
| } | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitWideIntMul(WideIntMul* curr) { | |
| prepareColor(o); | |
| switch (curr->op) { | |
| case MulWideSInt64: { | |
| o << "i64.mul_wide_s"; | |
| break; | |
| } | |
| case MulWideUInt64: { | |
| o << "i64.mul_wide_u"; | |
| break; | |
| } | |
| } | |
| restoreNormalColor(o); | |
| } | |
| void visitSelect(Select* curr) { | |
| prepareColor(o) << "select"; | |
| restoreNormalColor(o); | |
| if (curr->type.isRef()) { | |
| o << ' '; | |
| printResultType(curr->type); | |
| } | |
| } | |
| void visitDrop(Drop* curr) { | |
| if (curr->value->type.isTuple()) { | |
| printMedium(o, "tuple.drop "); | |
| o << curr->value->type.size(); | |
| } else { | |
| printMedium(o, "drop"); | |
| } | |
| } | |
| void visitReturn(Return* curr) { printMedium(o, "return"); } | |
| void visitMemorySize(MemorySize* curr) { | |
| printMedium(o, "memory.size"); | |
| printMemoryName(curr->memory, o, wasm); | |
| } | |
| void visitMemoryGrow(MemoryGrow* curr) { | |
| printMedium(o, "memory.grow"); | |
| printMemoryName(curr->memory, o, wasm); | |
| } | |
| void visitRefNull(RefNull* curr) { | |
| printMedium(o, "ref.null "); | |
| printHeapTypeName(curr->type.getHeapType()); | |
| } | |
| void visitRefIsNull(RefIsNull* curr) { printMedium(o, "ref.is_null"); } | |
| void visitRefFunc(RefFunc* curr) { | |
| printMedium(o, "ref.func "); | |
| curr->func.print(o); | |
| } | |
| void visitRefEq(RefEq* curr) { printMedium(o, "ref.eq"); } | |
| void visitTableGet(TableGet* curr) { | |
| printMedium(o, "table.get "); | |
| curr->table.print(o); | |
| } | |
| void visitTableSet(TableSet* curr) { | |
| printMedium(o, "table.set "); | |
| curr->table.print(o); | |
| } | |
| void visitTableSize(TableSize* curr) { | |
| printMedium(o, "table.size "); | |
| curr->table.print(o); | |
| } | |
| void visitTableGrow(TableGrow* curr) { | |
| printMedium(o, "table.grow "); | |
| curr->table.print(o); | |
| } | |
| void visitTableFill(TableFill* curr) { | |
| printMedium(o, "table.fill "); | |
| curr->table.print(o); | |
| } | |
| void visitTableCopy(TableCopy* curr) { | |
| printMedium(o, "table.copy "); | |
| curr->destTable.print(o); | |
| o << ' '; | |
| curr->sourceTable.print(o); | |
| } | |
| void visitTableInit(TableInit* curr) { | |
| printMedium(o, "table.init "); | |
| curr->table.print(o); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitElemDrop(ElemDrop* curr) { | |
| printMedium(o, "elem.drop "); | |
| curr->segment.print(o); | |
| } | |
| void visitTry(Try* curr) { | |
| printMedium(o, "try"); | |
| if (curr->name.is()) { | |
| o << ' '; | |
| curr->name.print(o); | |
| } | |
| if (curr->type.isConcrete()) { | |
| o << ' '; | |
| printBlockType(Signature(Type::none, curr->type)); | |
| } | |
| } | |
| void visitTryTable(TryTable* curr) { | |
| printMedium(o, "try_table"); | |
| if (curr->type.isConcrete()) { | |
| o << ' '; | |
| printBlockType(Signature(Type::none, curr->type)); | |
| } | |
| for (Index i = 0; i < curr->catchTags.size(); i++) { | |
| o << " ("; | |
| if (curr->catchTags[i]) { | |
| printMedium(o, curr->catchRefs[i] ? "catch_ref " : "catch "); | |
| curr->catchTags[i].print(o); | |
| o << ' '; | |
| } else { | |
| printMedium(o, curr->catchRefs[i] ? "catch_all_ref " : "catch_all "); | |
| } | |
| curr->catchDests[i].print(o); | |
| o << ')'; | |
| } | |
| } | |
| void visitThrow(Throw* curr) { | |
| printMedium(o, "throw "); | |
| curr->tag.print(o); | |
| } | |
| void visitRethrow(Rethrow* curr) { | |
| printMedium(o, "rethrow "); | |
| curr->target.print(o); | |
| } | |
| void visitThrowRef(ThrowRef* curr) { printMedium(o, "throw_ref"); } | |
| void visitNop(Nop* curr) { printMinor(o, "nop"); } | |
| void visitUnreachable(Unreachable* curr) { printMinor(o, "unreachable"); } | |
| void visitPop(Pop* curr) { | |
| prepareColor(o) << "pop "; | |
| printType(curr->type); | |
| restoreNormalColor(o); | |
| } | |
| void visitTupleMake(TupleMake* curr) { | |
| printMedium(o, "tuple.make "); | |
| o << curr->operands.size(); | |
| } | |
| void visitTupleExtract(TupleExtract* curr) { | |
| printMedium(o, "tuple.extract "); | |
| // If the tuple is unreachable, its size will be reported as 1, but that's | |
| // not a valid tuple size. The size we print mostly doesn't matter if the | |
| // tuple is unreachable, but it does have to be valid. | |
| o << std::max( | |
| {curr->tuple->type.size(), size_t(2), size_t(curr->index + 1)}) | |
| << " "; | |
| o << curr->index; | |
| } | |
| void visitRefI31(RefI31* curr) { | |
| bool shared = | |
| curr->type != Type::unreachable && curr->type.getHeapType().isShared(); | |
| printMedium(o, shared ? "ref.i31_shared" : "ref.i31"); | |
| } | |
| void visitI31Get(I31Get* curr) { | |
| printMedium(o, curr->signed_ ? "i31.get_s" : "i31.get_u"); | |
| } | |
| void visitCallRef(CallRef* curr) { | |
| printMedium(o, curr->isReturn ? "return_call_ref " : "call_ref "); | |
| printHeapTypeName(curr->target->type.getHeapType()); | |
| } | |
| void visitRefTest(RefTest* curr) { | |
| printMedium(o, "ref.test "); | |
| printType(curr->castType); | |
| } | |
| void visitRefCast(RefCast* curr) { | |
| if (curr->desc) { | |
| printMedium(o, "ref.cast_desc_eq "); | |
| } else { | |
| printMedium(o, "ref.cast "); | |
| } | |
| if (curr->type != Type::unreachable) { | |
| printType(curr->type); | |
| } else { | |
| // We can still recover a valid result type from the type of the | |
| // descriptor. | |
| auto described = curr->desc->type.getHeapType().getDescribedType(); | |
| if (described) { | |
| printType( | |
| Type(*described, NonNullable, curr->desc->type.getExactness())); | |
| } else { | |
| // Invalid, so it doesn't matter what we print. | |
| printType(Type::unreachable); | |
| } | |
| } | |
| } | |
| void visitRefGetDesc(RefGetDesc* curr) { | |
| printMedium(o, "ref.get_desc "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| } | |
| void visitBrOn(BrOn* curr) { | |
| switch (curr->op) { | |
| case BrOnNull: | |
| printMedium(o, "br_on_null "); | |
| curr->name.print(o); | |
| return; | |
| case BrOnNonNull: | |
| printMedium(o, "br_on_non_null "); | |
| curr->name.print(o); | |
| return; | |
| case BrOnCast: | |
| case BrOnCastDescEq: | |
| case BrOnCastFail: | |
| case BrOnCastDescEqFail: | |
| switch (curr->op) { | |
| case BrOnCast: | |
| printMedium(o, "br_on_cast"); | |
| break; | |
| case BrOnCastFail: | |
| printMedium(o, "br_on_cast_fail"); | |
| break; | |
| case BrOnCastDescEq: | |
| printMedium(o, "br_on_cast_desc_eq"); | |
| break; | |
| case BrOnCastDescEqFail: | |
| printMedium(o, "br_on_cast_desc_eq_fail"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("unexpected op"); | |
| } | |
| o << ' '; | |
| curr->name.print(o); | |
| o << ' '; | |
| if (curr->ref->type == Type::unreachable) { | |
| // Need to print some reference type in the correct hierarchy rather | |
| // than unreachable, and the cast type itself is the best possible | |
| // option. | |
| printType(curr->castType); | |
| } else { | |
| printType(curr->ref->type); | |
| } | |
| o << ' '; | |
| printType(curr->castType); | |
| return; | |
| printMedium(o, | |
| curr->op == BrOnCastFail ? "br_on_cast_fail " | |
| : "br_on_cast_desc_eq_fail "); | |
| curr->name.print(o); | |
| o << ' '; | |
| if (curr->ref->type == Type::unreachable) { | |
| printType(curr->castType); | |
| } else { | |
| printType(curr->ref->type); | |
| } | |
| o << ' '; | |
| printType(curr->castType); | |
| return; | |
| } | |
| WASM_UNREACHABLE("Unexpected br_on* op"); | |
| } | |
| void visitStructNew(StructNew* curr) { | |
| printMedium(o, "struct.new"); | |
| if (curr->isWithDefault()) { | |
| printMedium(o, "_default"); | |
| } | |
| if (curr->desc) { | |
| printMedium(o, "_desc"); | |
| } | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| } | |
| void printFieldName(HeapType type, Index index) { | |
| auto names = parent.typePrinter.getNames(type).fieldNames; | |
| if (auto it = names.find(index); it != names.end()) { | |
| it->second.print(o); | |
| } else { | |
| o << index; | |
| } | |
| } | |
| void printMemoryOrder(MemoryOrder order) { | |
| switch (order) { | |
| // Unordered should have a different base instruction, so there is nothing | |
| // to print. We could be explicit and print seqcst, but we choose not to | |
| // for more concise output. | |
| case MemoryOrder::Unordered: | |
| case MemoryOrder::SeqCst: | |
| break; | |
| case MemoryOrder::AcqRel: | |
| o << " acqrel"; | |
| break; | |
| case MemoryOrder::Relaxed: | |
| o << " relaxed"; | |
| break; | |
| } | |
| } | |
| void visitStructGet(StructGet* curr) { | |
| auto heapType = curr->ref->type.getHeapType(); | |
| const auto& field = heapType.getStruct().fields[curr->index]; | |
| printMedium(o, "struct"); | |
| if (curr->order != MemoryOrder::Unordered) { | |
| printMedium(o, ".atomic"); | |
| } | |
| if (field.type == Type::i32 && field.packedType != Field::NotPacked) { | |
| if (curr->signed_) { | |
| printMedium(o, ".get_s"); | |
| } else { | |
| printMedium(o, ".get_u"); | |
| } | |
| } else { | |
| printMedium(o, ".get"); | |
| } | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| printHeapTypeName(heapType); | |
| o << ' '; | |
| printFieldName(heapType, curr->index); | |
| } | |
| void visitStructSet(StructSet* curr) { | |
| if (curr->order == MemoryOrder::Unordered) { | |
| printMedium(o, "struct.set"); | |
| } else { | |
| printMedium(o, "struct.atomic.set"); | |
| } | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| auto heapType = curr->ref->type.getHeapType(); | |
| printHeapTypeName(heapType); | |
| o << ' '; | |
| printFieldName(heapType, curr->index); | |
| } | |
| void visitStructRMW(StructRMW* curr) { | |
| prepareColor(o); | |
| o << "struct.atomic.rmw."; | |
| printAtomicRMWOp(curr->op); | |
| restoreNormalColor(o); | |
| printMemoryOrder(curr->order); | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| auto heapType = curr->ref->type.getHeapType(); | |
| printHeapTypeName(heapType); | |
| o << ' '; | |
| printFieldName(heapType, curr->index); | |
| } | |
| void visitStructCmpxchg(StructCmpxchg* curr) { | |
| prepareColor(o); | |
| o << "struct.atomic.rmw.cmpxchg"; | |
| restoreNormalColor(o); | |
| printMemoryOrder(curr->order); | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| auto heapType = curr->ref->type.getHeapType(); | |
| printHeapTypeName(heapType); | |
| o << ' '; | |
| printFieldName(heapType, curr->index); | |
| } | |
| void visitStructWait(StructWait* curr) { | |
| printMedium(o, "struct.wait"); | |
| o << ' '; | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| o << ' '; | |
| o << curr->index; | |
| } | |
| void visitWaitqueueNew(WaitqueueNew* curr) { | |
| printMedium(o, "waitqueue.new"); | |
| } | |
| void visitWaitqueueNotify(WaitqueueNotify* curr) { | |
| printMedium(o, "waitqueue.notify"); | |
| } | |
| void visitPublish(Publish* curr) { printMedium(o, "publish"); } | |
| void visitArrayNew(ArrayNew* curr) { | |
| printMedium(o, "array.new"); | |
| if (curr->isWithDefault()) { | |
| printMedium(o, "_default"); | |
| } | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| } | |
| void visitArrayNewData(ArrayNewData* curr) { | |
| printMedium(o, "array.new_data"); | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitArrayNewElem(ArrayNewElem* curr) { | |
| printMedium(o, "array.new_elem"); | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitArrayNewFixed(ArrayNewFixed* curr) { | |
| printMedium(o, "array.new_fixed"); | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| o << ' '; | |
| o << curr->values.size(); | |
| } | |
| void visitArrayGet(ArrayGet* curr) { | |
| const auto& element = curr->ref->type.getHeapType().getArray().element; | |
| printMedium(o, "array"); | |
| if (curr->order != MemoryOrder::Unordered) { | |
| printMedium(o, ".atomic"); | |
| } | |
| if (element.type == Type::i32 && element.packedType != Field::NotPacked) { | |
| if (curr->signed_) { | |
| printMedium(o, ".get_s"); | |
| } else { | |
| printMedium(o, ".get_u"); | |
| } | |
| } else { | |
| printMedium(o, ".get"); | |
| } | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| } | |
| void visitArraySet(ArraySet* curr) { | |
| if (curr->order == MemoryOrder::Unordered) { | |
| printMedium(o, "array.set"); | |
| } else { | |
| printMedium(o, "array.atomic.set"); | |
| } | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| } | |
| void visitArrayLoad(ArrayLoad* curr) { | |
| prepareColor(o) << forceConcrete(curr->type); | |
| o << ".load"; | |
| if (curr->type != Type::unreachable && | |
| curr->bytes < curr->type.getByteSize()) { | |
| printMemoryPostfix(curr->bytes, curr->type); | |
| o << (curr->signed_ ? "_s" : "_u"); | |
| } | |
| o << " "; | |
| restoreNormalColor(o); | |
| o << '('; | |
| printMinor(o, "type "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| o << ')'; | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->bytes) { | |
| o << " align=" << curr->align; | |
| } | |
| } | |
| void visitArrayStore(ArrayStore* curr) { | |
| prepareColor(o) << forceConcrete(curr->value->type); | |
| o << ".store"; | |
| printStorePostfix(curr->bytes, curr->value->type); | |
| o << " "; | |
| restoreNormalColor(o); | |
| o << '('; | |
| printMinor(o, "type "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| o << ')'; | |
| if (curr->offset) { | |
| o << " offset=" << curr->offset; | |
| } | |
| if (curr->align != curr->bytes) { | |
| o << " align=" << curr->align; | |
| } | |
| } | |
| void visitArrayLen(ArrayLen* curr) { printMedium(o, "array.len"); } | |
| void visitArrayCopy(ArrayCopy* curr) { | |
| printMedium(o, "array.copy "); | |
| printHeapTypeName(curr->destRef->type.getHeapType()); | |
| o << ' '; | |
| printHeapTypeName(curr->srcRef->type.getHeapType()); | |
| } | |
| void visitArrayFill(ArrayFill* curr) { | |
| printMedium(o, "array.fill "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| } | |
| void visitArrayInitData(ArrayInitData* curr) { | |
| printMedium(o, "array.init_data "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitArrayInitElem(ArrayInitElem* curr) { | |
| printMedium(o, "array.init_elem "); | |
| printHeapTypeName(curr->ref->type.getHeapType()); | |
| o << ' '; | |
| curr->segment.print(o); | |
| } | |
| void visitArrayRMW(ArrayRMW* curr) { | |
| prepareColor(o); | |
| o << "array.atomic.rmw."; | |
| printAtomicRMWOp(curr->op); | |
| restoreNormalColor(o); | |
| printMemoryOrder(curr->order); | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| auto heapType = curr->ref->type.getHeapType(); | |
| printHeapTypeName(heapType); | |
| } | |
| void visitArrayCmpxchg(ArrayCmpxchg* curr) { | |
| prepareColor(o); | |
| o << "array.atomic.rmw.cmpxchg"; | |
| restoreNormalColor(o); | |
| printMemoryOrder(curr->order); | |
| printMemoryOrder(curr->order); | |
| o << ' '; | |
| auto heapType = curr->ref->type.getHeapType(); | |
| printHeapTypeName(heapType); | |
| } | |
| void visitRefAs(RefAs* curr) { | |
| switch (curr->op) { | |
| case RefAsNonNull: | |
| printMedium(o, "ref.as_non_null"); | |
| break; | |
| case AnyConvertExtern: | |
| printMedium(o, "any.convert_extern"); | |
| break; | |
| case ExternConvertAny: | |
| printMedium(o, "extern.convert_any"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("invalid ref.is_*"); | |
| } | |
| } | |
| void visitStringNew(StringNew* curr) { | |
| switch (curr->op) { | |
| case StringNewLossyUTF8Array: | |
| printMedium(o, "string.new_lossy_utf8_array"); | |
| break; | |
| case StringNewWTF16Array: | |
| printMedium(o, "string.new_wtf16_array"); | |
| break; | |
| case StringNewFromCodePoint: | |
| printMedium(o, "string.from_code_point"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("invalid string.new*"); | |
| } | |
| } | |
| void visitStringConst(StringConst* curr) { | |
| printMedium(o, "string.const "); | |
| // Re-encode from WTF-16 to WTF-8. | |
| std::stringstream wtf8; | |
| [[maybe_unused]] bool valid = | |
| String::convertWTF16ToWTF8(wtf8, curr->string.view()); | |
| assert(valid); | |
| // TODO: Use wtf8.view() once we have C++20. | |
| String::printEscaped(o, wtf8.str()); | |
| } | |
| void visitStringMeasure(StringMeasure* curr) { | |
| switch (curr->op) { | |
| case StringMeasureUTF8: | |
| printMedium(o, "string.measure_utf8"); | |
| break; | |
| case StringMeasureWTF16: | |
| printMedium(o, "string.measure_wtf16"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("invalid string.measure*"); | |
| } | |
| } | |
| void visitStringEncode(StringEncode* curr) { | |
| switch (curr->op) { | |
| case StringEncodeLossyUTF8Array: | |
| printMedium(o, "string.encode_lossy_utf8_array"); | |
| break; | |
| case StringEncodeWTF16Array: | |
| printMedium(o, "string.encode_wtf16_array"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("invalid string.encode*"); | |
| } | |
| } | |
| void visitStringConcat(StringConcat* curr) { | |
| printMedium(o, "string.concat"); | |
| } | |
| void visitStringEq(StringEq* curr) { | |
| switch (curr->op) { | |
| case StringEqEqual: | |
| printMedium(o, "string.eq"); | |
| break; | |
| case StringEqCompare: | |
| printMedium(o, "string.compare"); | |
| break; | |
| default: | |
| WASM_UNREACHABLE("invalid string.eq*"); | |
| } | |
| } | |
| void visitStringTest(StringTest* curr) { printMedium(o, "string.test"); } | |
| void visitStringWTF16Get(StringWTF16Get* curr) { | |
| printMedium(o, "stringview_wtf16.get_codeunit"); | |
| } | |
| void visitStringSliceWTF(StringSliceWTF* curr) { | |
| printMedium(o, "stringview_wtf16.slice"); | |
| } | |
| void visitContNew(ContNew* curr) { | |
| assert(curr->type.isContinuation()); | |
| printMedium(o, "cont.new "); | |
| printHeapTypeName(curr->type.getHeapType()); | |
| } | |
| void visitContBind(ContBind* curr) { | |
| assert(curr->cont->type.isContinuation() && curr->type.isContinuation()); | |
| printMedium(o, "cont.bind "); | |
| printHeapTypeName(curr->cont->type.getHeapType()); | |
| o << ' '; | |
| printHeapTypeName(curr->type.getHeapType()); | |
| } | |
| void visitSuspend(Suspend* curr) { | |
| printMedium(o, "suspend "); | |
| curr->tag.print(o); | |
| } | |
| template<typename ResumeType> | |
| static void handleResumeTable(std::ostream& o, ResumeType* curr) { | |
| static_assert(std::is_base_of<ResumeType, Resume>::value || | |
| std::is_base_of<ResumeType, ResumeThrow>::value); | |
| for (Index i = 0; i < curr->handlerTags.size(); i++) { | |
| o << " ("; | |
| printMedium(o, "on "); | |
| curr->handlerTags[i].print(o); | |
| o << ' '; | |
| if (curr->handlerBlocks[i].isNull()) { | |
| o << "switch"; | |
| } else { | |
| curr->handlerBlocks[i].print(o); | |
| } | |
| o << ')'; | |
| } | |
| } | |
| void visitResume(Resume* curr) { | |
| assert(curr->cont->type.isContinuation()); | |
| printMedium(o, "resume"); | |
| o << ' '; | |
| printHeapTypeName(curr->cont->type.getHeapType()); | |
| handleResumeTable(o, curr); | |
| } | |
| void visitResumeThrow(ResumeThrow* curr) { | |
| assert(curr->cont->type.isContinuation()); | |
| printMedium(o, "resume_throw"); | |
| if (!curr->tag) { | |
| printMedium(o, "_ref"); | |
| } | |
| o << ' '; | |
| printHeapTypeName(curr->cont->type.getHeapType()); | |
| if (curr->tag) { | |
| o << ' '; | |
| curr->tag.print(o); | |
| } | |
| handleResumeTable(o, curr); | |
| } | |
| void visitStackSwitch(StackSwitch* curr) { | |
| assert(curr->cont->type.isContinuation()); | |
| printMedium(o, "switch"); | |
| o << ' '; | |
| printHeapTypeName(curr->cont->type.getHeapType()); | |
| o << ' '; | |
| curr->tag.print(o); | |
| } | |
| }; | |
| void PrintSExpression::setModule(Module* module) { | |
| currModule = module; | |
| if (module) { | |
| heapTypes = ModuleUtils::getOptimizedIndexedHeapTypes(*module).types; | |
| for (auto type : heapTypes) { | |
| if (type.isSignature()) { | |
| signatureTypes.insert({type.getSignature(), type}); | |
| } | |
| } | |
| } else { | |
| heapTypes = {}; | |
| signatureTypes = {}; | |
| } | |
| // Reset the type printer for this module's types (or absence thereof). | |
| typePrinter.~TypePrinter(); | |
| new (&typePrinter) TypePrinter(*this, heapTypes); | |
| } | |
| std::ostream& PrintSExpression::printPrefixedTypes(const char* prefix, | |
| Type type) { | |
| o << '(' << prefix; | |
| if (type == Type::none) { | |
| return o << ')'; | |
| } | |
| if (type.isTuple()) { | |
| // Tuple types are not printed in parens, we can just emit them one after | |
| // the other in the same list as the "result". | |
| for (auto t : type) { | |
| o << ' '; | |
| printType(t); | |
| } | |
| } else { | |
| o << ' '; | |
| printType(type); | |
| } | |
| o << ')'; | |
| return o; | |
| } | |
| void PrintSExpression::printDebugLocation( | |
| const std::optional<Function::DebugLocation>& location) { | |
| if (minify) { | |
| return; | |
| } | |
| // Do not skip repeated debug info in full mode, for less-confusing debugging: | |
| // full mode prints out everything in the most verbose manner. | |
| if (lastPrintedLocation == location && indent > lastPrintIndent && !full) { | |
| return; | |
| } | |
| lastPrintedLocation = location; | |
| lastPrintIndent = indent; | |
| if (!location) { | |
| o << ";;@\n"; | |
| } else { | |
| auto fileName = currModule->debugInfoFileNames[location->fileIndex]; | |
| o << ";;@ " << fileName << ":" << location->lineNumber << ":" | |
| << location->columnNumber; | |
| if (location->symbolNameIndex) { | |
| auto symbolName = | |
| currModule->debugInfoSymbolNames[*(location->symbolNameIndex)]; | |
| o << ":" << symbolName; | |
| } | |
| o << '\n'; | |
| } | |
| doIndent(o, indent); | |
| } | |
| void PrintSExpression::printMetadata(Expression* curr) { | |
| if (currFunction) { | |
| // Show a debug location, if there is one. | |
| if (auto iter = currFunction->debugLocations.find(curr); | |
| iter != currFunction->debugLocations.end()) { | |
| printDebugLocation(iter->second); | |
| } else { | |
| printDebugLocation(std::nullopt); | |
| } | |
| // Show a binary position, if there is one. | |
| if (debugInfo) { | |
| if (auto iter = currFunction->expressionLocations.find(curr); | |
| iter != currFunction->expressionLocations.end()) { | |
| Colors::grey(o); | |
| const auto& span = iter->second; | |
| o << ";; code offset: 0x" << std::hex << span.start << " - 0x" | |
| << span.end << std::dec << '\n'; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| } | |
| // Show code annotations. | |
| printCodeAnnotations(curr); | |
| } | |
| } | |
| void PrintSExpression::printDebugDelimiterLocation(Expression* curr, Index i) { | |
| if (currFunction && debugInfo) { | |
| auto iter = currFunction->delimiterLocations.find(curr); | |
| if (iter != currFunction->delimiterLocations.end()) { | |
| auto& locations = iter->second; | |
| Colors::grey(o); | |
| o << ";; code offset: 0x" << std::hex << locations[i] << std::dec << '\n'; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| } | |
| } | |
| void PrintSExpression::printCodeAnnotations(Expression* curr) { | |
| if (auto iter = currFunction->codeAnnotations.find(curr); | |
| iter != currFunction->codeAnnotations.end()) { | |
| printCodeAnnotations(iter->second); | |
| } | |
| } | |
| void PrintSExpression::printCodeAnnotations(const CodeAnnotation& annotation) { | |
| if (annotation.branchLikely) { | |
| Colors::grey(o); | |
| o << "(@" << Annotations::BranchHint << " \"\\0" | |
| << (*annotation.branchLikely ? "1" : "0") << "\")\n"; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| if (annotation.inline_) { | |
| Colors::grey(o); | |
| std::ofstream saved; | |
| saved.copyfmt(o); | |
| o << "(@" << Annotations::InlineHint << " \"\\" << std::hex | |
| << std::setfill('0') << std::setw(2) << int(*annotation.inline_) | |
| << "\")\n"; | |
| o.copyfmt(saved); | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| if (annotation.removableIfUnused) { | |
| Colors::grey(o); | |
| o << "(@" << Annotations::RemovableIfUnusedHint << ")\n"; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| if (annotation.jsCalled) { | |
| Colors::grey(o); | |
| o << "(@" << Annotations::JSCalledHint << ")\n"; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| if (annotation.idempotent) { | |
| Colors::grey(o); | |
| o << "(@" << Annotations::IdempotentHint << ")\n"; | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| if (annotation.toolchainInline) { | |
| Colors::grey(o); | |
| std::ofstream saved; | |
| saved.copyfmt(o); | |
| o << "(@" << Annotations::ToolchainInlineHint << " \"\\" << std::hex | |
| << std::setfill('0') << std::setw(2) << int(*annotation.toolchainInline) | |
| << "\")\n"; | |
| o.copyfmt(saved); | |
| restoreNormalColor(o); | |
| doIndent(o, indent); | |
| } | |
| } | |
| void PrintSExpression::printExpressionContents(Expression* curr) { | |
| PrintExpressionContents(*this).visit(curr); | |
| } | |
| void PrintSExpression::incIndent() { | |
| if (minify) { | |
| return; | |
| } | |
| o << '\n'; | |
| indent++; | |
| } | |
| void PrintSExpression::decIndent() { | |
| if (!minify) { | |
| assert(indent > 0); | |
| indent--; | |
| doIndent(o, indent); | |
| } | |
| o << ')'; | |
| } | |
| void PrintSExpression::printFullLine(Expression* expression) { | |
| if (!minify) { | |
| doIndent(o, indent); | |
| } | |
| visit(expression); | |
| if (full) { | |
| o << " (; "; | |
| printTypeOrName(expression->type, o, currModule); | |
| o << " ;)"; | |
| } | |
| o << maybeNewLine; | |
| } | |
| void PrintSExpression::maybePrintImplicitBlock(Expression* curr) { | |
| auto block = curr->dynCast<Block>(); | |
| if (!full && block && block->name.isNull()) { | |
| for (auto expression : block->list) { | |
| printFullLine(expression); | |
| } | |
| } else { | |
| printFullLine(curr); | |
| } | |
| } | |
| void PrintSExpression::visitExpression(Expression* curr) { | |
| if (Properties::hasUnwritableTypeImmediate(curr)) { | |
| printUnreachableReplacement(curr); | |
| return; | |
| } | |
| o << '('; | |
| printExpressionContents(curr); | |
| auto it = ChildIterator(curr); | |
| if (!it.children.empty()) { | |
| incIndent(); | |
| for (auto* child : it) { | |
| printFullLine(child); | |
| } | |
| decIndent(); | |
| } else { | |
| o << ')'; | |
| } | |
| } | |
| void PrintSExpression::visitBlock(Block* curr) { | |
| // special-case Block, because Block nesting (in their first element) can be | |
| // incredibly deep | |
| std::vector<Block*> stack; | |
| while (1) { | |
| if (stack.size() > 0) { | |
| doIndent(o, indent); | |
| printMetadata(curr); | |
| } | |
| stack.push_back(curr); | |
| o << '('; | |
| printExpressionContents(curr); | |
| if (full) { | |
| o << " (; "; | |
| printTypeOrName(curr->type, o, currModule); | |
| o << " ;)"; | |
| } | |
| incIndent(); | |
| if (curr->list.size() > 0 && curr->list[0]->is<Block>()) { | |
| // recurse into the first element | |
| curr = curr->list[0]->cast<Block>(); | |
| continue; | |
| } else { | |
| break; // that's all we can recurse, start to unwind | |
| } | |
| } | |
| controlFlowDepth += stack.size(); | |
| auto* top = stack.back(); | |
| while (stack.size() > 0) { | |
| curr = stack.back(); | |
| stack.pop_back(); | |
| auto& list = curr->list; | |
| for (size_t i = 0; i < list.size(); i++) { | |
| if (curr != top && i == 0) { | |
| // one of the block recursions we already handled | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end block"; | |
| auto* child = list[0]->cast<Block>(); | |
| if (child->name.is()) { | |
| o << ' ' << child->name; | |
| } | |
| } | |
| o << '\n'; | |
| continue; | |
| } | |
| printFullLine(list[i]); | |
| } | |
| controlFlowDepth--; | |
| } | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end block"; | |
| if (curr->name.is()) { | |
| o << ' ' << curr->name; | |
| } | |
| } | |
| } | |
| void PrintSExpression::visitIf(If* curr) { | |
| controlFlowDepth++; | |
| o << '('; | |
| printExpressionContents(curr); | |
| incIndent(); | |
| printFullLine(curr->condition); | |
| doIndent(o, indent); | |
| o << "(then"; | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->ifTrue); | |
| decIndent(); | |
| o << maybeNewLine; | |
| if (curr->ifFalse) { | |
| doIndent(o, indent); | |
| o << "(else"; | |
| incIndent(); | |
| // Note: debug info here is not used as LLVM does not emit ifs, and since | |
| // LLVM is the main source of DWARF, effectively we never encounter ifs | |
| // with DWARF. | |
| printDebugDelimiterLocation(curr, BinaryLocations::Else); | |
| maybePrintImplicitBlock(curr->ifFalse); | |
| decIndent(); | |
| o << maybeNewLine; | |
| } | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end if"; | |
| } | |
| controlFlowDepth--; | |
| } | |
| void PrintSExpression::visitLoop(Loop* curr) { | |
| controlFlowDepth++; | |
| o << '('; | |
| printExpressionContents(curr); | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->body); | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end loop"; | |
| if (curr->name.is()) { | |
| o << ' ' << curr->name; | |
| } | |
| } | |
| controlFlowDepth--; | |
| } | |
| // try-catch-end is written in the folded wat format as | |
| // (try | |
| // (do | |
| // ... | |
| // ) | |
| // (catch $e | |
| // ... | |
| // ) | |
| // ... | |
| // (catch_all | |
| // ... | |
| // ) | |
| // ) | |
| // The parenthesis wrapping do/catch/catch_all is just a syntax and does not | |
| // affect nested depths of instructions within. | |
| // | |
| // try-delegate is written in the folded format as | |
| // (try | |
| // (do | |
| // ... | |
| // ) | |
| // (delegate $label) | |
| // ) | |
| // When the 'delegate' delegates to the caller, we write the argument as an | |
| // immediate. | |
| void PrintSExpression::visitTry(Try* curr) { | |
| controlFlowDepth++; | |
| o << '('; | |
| printExpressionContents(curr); | |
| incIndent(); | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "do"); | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->body); | |
| decIndent(); | |
| o << "\n"; | |
| for (size_t i = 0; i < curr->catchTags.size(); i++) { | |
| doIndent(o, indent); | |
| printDebugDelimiterLocation(curr, i); | |
| o << '('; | |
| printMedium(o, "catch "); | |
| curr->catchTags[i].print(o); | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->catchBodies[i]); | |
| decIndent(); | |
| o << "\n"; | |
| } | |
| if (curr->hasCatchAll()) { | |
| doIndent(o, indent); | |
| printDebugDelimiterLocation(curr, curr->catchTags.size()); | |
| o << '('; | |
| printMedium(o, "catch_all"); | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->catchBodies.back()); | |
| decIndent(); | |
| o << "\n"; | |
| } | |
| controlFlowDepth--; | |
| if (curr->isDelegate()) { | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "delegate "); | |
| if (curr->delegateTarget == DELEGATE_CALLER_TARGET) { | |
| o << controlFlowDepth; | |
| } else { | |
| curr->delegateTarget.print(o); | |
| } | |
| o << ")\n"; | |
| } | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end try"; | |
| } | |
| } | |
| void PrintSExpression::visitTryTable(TryTable* curr) { | |
| controlFlowDepth++; | |
| o << '('; | |
| printExpressionContents(curr); | |
| incIndent(); | |
| maybePrintImplicitBlock(curr->body); | |
| decIndent(); | |
| if (full) { | |
| o << " ;; end try_table"; | |
| } | |
| controlFlowDepth--; | |
| } | |
| void PrintSExpression::printUnreachableReplacement(Expression* curr) { | |
| // Emit a block with drops of the children. | |
| o << "(block"; | |
| if (!minify) { | |
| o << " ;; (replaces unreachable " << getExpressionName(curr) | |
| << " we can't emit)"; | |
| } | |
| incIndent(); | |
| for (auto* child : ChildIterator(curr)) { | |
| Drop drop; | |
| drop.value = child; | |
| printFullLine(&drop); | |
| } | |
| Unreachable unreachable; | |
| printFullLine(&unreachable); | |
| decIndent(); | |
| } | |
| static bool requiresExplicitFuncType(HeapType type) { | |
| // When the `(type $f)` in a function's typeuse is omitted, the typeuse | |
| // matches or declares an MVP function type. When the intended type is not an | |
| // MVP function type, we therefore need the explicit `(type $f)`. | |
| return type.isOpen() || type.isShared() || type.getRecGroup().size() > 1; | |
| } | |
| void PrintSExpression::handleSignature(Function* curr, | |
| bool printImplicitNames) { | |
| o << '('; | |
| printMajor(o, "func "); | |
| curr->name.print(o); | |
| if (curr->imported() && curr->type.isExact()) { | |
| o << " (exact"; | |
| } | |
| if ((currModule && currModule->features.hasGC()) || | |
| requiresExplicitFuncType(curr->type.getHeapType())) { | |
| o << " (type "; | |
| printHeapTypeName(curr->type.getHeapType()) << ')'; | |
| if (full) { | |
| // Print the full type in a comment. TODO the spec may add this too | |
| o << " (; "; | |
| printTypeOrName(curr->type, o, currModule); | |
| o << " ;)"; | |
| } | |
| } | |
| bool inParam = false; | |
| Index i = 0; | |
| for (const auto& param : curr->getParams()) { | |
| auto hasName = printImplicitNames || curr->hasLocalName(i); | |
| if (hasName && inParam) { | |
| o << ')' << maybeSpace; | |
| inParam = false; | |
| } else if (inParam) { | |
| o << ' '; | |
| } else { | |
| o << maybeSpace; | |
| } | |
| if (!inParam) { | |
| o << '('; | |
| printMinor(o, "param "); | |
| inParam = true; | |
| } | |
| if (hasName) { | |
| printLocal(i, currFunction, o); | |
| o << ' '; | |
| } | |
| printType(param); | |
| if (hasName) { | |
| o << ')'; | |
| inParam = false; | |
| } | |
| ++i; | |
| } | |
| if (inParam) { | |
| o << ')'; | |
| } | |
| if (curr->getResults() != Type::none) { | |
| o << maybeSpace; | |
| printResultType(curr->getResults()); | |
| } | |
| if (curr->imported() && curr->type.isExact()) { | |
| o << ')'; | |
| } | |
| } | |
| void PrintSExpression::visitExport(Export* curr) { | |
| o << '('; | |
| printMedium(o, "export "); | |
| std::stringstream escaped; | |
| String::printEscaped(escaped, curr->name.view()); | |
| printText(o, escaped.str(), false) << " ("; | |
| switch (curr->kind) { | |
| case ExternalKind::Function: | |
| o << "func"; | |
| break; | |
| case ExternalKind::Table: | |
| o << "table"; | |
| break; | |
| case ExternalKind::Memory: | |
| o << "memory"; | |
| break; | |
| case ExternalKind::Global: | |
| o << "global"; | |
| break; | |
| case ExternalKind::Tag: | |
| o << "tag"; | |
| break; | |
| case ExternalKind::Invalid: | |
| WASM_UNREACHABLE("invalid ExternalKind"); | |
| } | |
| o << ' '; | |
| // TODO: specific case for type exports | |
| curr->getInternalName()->print(o) << "))"; | |
| } | |
| void PrintSExpression::emitImportHeader(Importable* curr) { | |
| printMedium(o, "import "); | |
| std::stringstream escapedModule, escapedBase; | |
| String::printEscaped(escapedModule, curr->module.view()); | |
| String::printEscaped(escapedBase, curr->base.view()); | |
| printText(o, escapedModule.str(), false) << ' '; | |
| printText(o, escapedBase.str(), false) << ' '; | |
| } | |
| void PrintSExpression::visitGlobal(Global* curr) { | |
| if (curr->imported()) { | |
| visitImportedGlobal(curr); | |
| } else { | |
| visitDefinedGlobal(curr); | |
| } | |
| } | |
| void PrintSExpression::emitGlobalType(Global* curr) { | |
| if (curr->mutable_) { | |
| o << "(mut "; | |
| printType(curr->type) << ')'; | |
| } else { | |
| printType(curr->type); | |
| } | |
| } | |
| void PrintSExpression::visitImportedGlobal(Global* curr) { | |
| doIndent(o, indent); | |
| o << '('; | |
| emitImportHeader(curr); | |
| o << "(global "; | |
| curr->name.print(o) << ' '; | |
| emitGlobalType(curr); | |
| o << "))" << maybeNewLine; | |
| } | |
| void PrintSExpression::visitDefinedGlobal(Global* curr) { | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "global "); | |
| curr->name.print(o) << ' '; | |
| emitGlobalType(curr); | |
| o << ' '; | |
| visit(curr->init); | |
| o << ')'; | |
| o << maybeNewLine; | |
| } | |
| void PrintSExpression::visitFunction(Function* curr) { | |
| if (curr->imported()) { | |
| visitImportedFunction(curr); | |
| } else if (curr->body == nullptr) { | |
| // We are in the middle of parsing the module and have not parsed this | |
| // function's code yet. Skip it. | |
| } else { | |
| visitDefinedFunction(curr); | |
| } | |
| } | |
| void PrintSExpression::visitImportedFunction(Function* curr) { | |
| doIndent(o, indent); | |
| currFunction = curr; | |
| lastPrintedLocation = std::nullopt; | |
| o << '('; | |
| emitImportHeader(curr); | |
| printCodeAnnotations(curr->funcAnnotations); | |
| handleSignature(curr); | |
| o << "))"; | |
| o << maybeNewLine; | |
| } | |
| void PrintSExpression::visitDefinedFunction(Function* curr) { | |
| doIndent(o, indent); | |
| currFunction = curr; | |
| lastPrintedLocation = std::nullopt; | |
| lastPrintIndent = 0; | |
| if (currFunction->prologLocation) { | |
| printDebugLocation(*currFunction->prologLocation); | |
| } | |
| printCodeAnnotations(curr->funcAnnotations); | |
| handleSignature(curr, true); | |
| incIndent(); | |
| for (size_t i = curr->getVarIndexBase(); i < curr->getNumLocals(); i++) { | |
| doIndent(o, indent); | |
| o << '('; | |
| printMinor(o, "local "); | |
| printLocal(i, currFunction, o) << ' '; | |
| printType(curr->getLocalType(i)) << ')'; | |
| o << maybeNewLine; | |
| } | |
| // Print the body. | |
| StackIR* stackIR = nullptr; | |
| if (moduleStackIR) { | |
| stackIR = moduleStackIR->getStackIROrNull(curr); | |
| } | |
| if (stackIR) { | |
| printStackIR(stackIR, *this); | |
| } else { | |
| // It is ok to emit a block here, as a function can directly contain a | |
| // list, even if our ast avoids that for simplicity. We can just do that | |
| // optimization here.. | |
| if (!full && curr->body->is<Block>() && | |
| curr->body->cast<Block>()->name.isNull()) { | |
| Block* block = curr->body->cast<Block>(); | |
| for (auto item : block->list) { | |
| printFullLine(item); | |
| } | |
| } else { | |
| printFullLine(curr->body); | |
| } | |
| assert(controlFlowDepth == 0); | |
| } | |
| if (currFunction->epilogLocation) { | |
| // Print last debug location: mix of decIndent and printDebugLocation | |
| // logic. | |
| doIndent(o, indent); | |
| if (!minify) { | |
| indent--; | |
| } | |
| printDebugLocation(*currFunction->epilogLocation); | |
| o << ')'; | |
| } else { | |
| decIndent(); | |
| } | |
| o << maybeNewLine; | |
| } | |
| void PrintSExpression::visitTag(Tag* curr) { | |
| if (curr->imported()) { | |
| visitImportedTag(curr); | |
| } else { | |
| visitDefinedTag(curr); | |
| } | |
| } | |
| void PrintSExpression::visitImportedTag(Tag* curr) { | |
| doIndent(o, indent); | |
| o << '('; | |
| emitImportHeader(curr); | |
| o << "(tag "; | |
| curr->name.print(o); | |
| o << maybeSpace; | |
| printTagType(curr->type); | |
| o << "))" << maybeNewLine; | |
| } | |
| void PrintSExpression::visitDefinedTag(Tag* curr) { | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "tag "); | |
| curr->name.print(o); | |
| o << maybeSpace; | |
| printTagType(curr->type); | |
| o << ')' << maybeNewLine; | |
| } | |
| void PrintSExpression::printTagType(HeapType type) { | |
| o << "(type "; | |
| printHeapTypeName(type); | |
| o << ')'; | |
| if (auto params = type.getSignature().params; params != Type::none) { | |
| o << maybeSpace << "(param"; | |
| for (auto t : params) { | |
| o << ' '; | |
| printType(t); | |
| } | |
| o << ')'; | |
| } | |
| if (auto results = type.getSignature().results; results != Type::none) { | |
| o << maybeSpace << "(result"; | |
| for (auto t : results) { | |
| o << ' '; | |
| printType(t); | |
| } | |
| o << ')'; | |
| } | |
| } | |
| void PrintSExpression::printTableHeader(Table* curr) { | |
| o << '('; | |
| printMedium(o, "table") << ' '; | |
| curr->name.print(o) << ' '; | |
| if (curr->is64()) { | |
| o << "i64 "; | |
| } | |
| o << curr->initial; | |
| if (curr->hasMax()) { | |
| o << ' ' << curr->max; | |
| } | |
| o << ' '; | |
| printType(curr->type); | |
| if (curr->init) { | |
| o << ' '; | |
| visit(curr->init); | |
| } | |
| o << ')'; | |
| } | |
| void PrintSExpression::visitTable(Table* curr) { | |
| if (curr->imported()) { | |
| doIndent(o, indent); | |
| o << '('; | |
| emitImportHeader(curr); | |
| printTableHeader(curr); | |
| o << ')' << maybeNewLine; | |
| } else { | |
| doIndent(o, indent); | |
| printTableHeader(curr); | |
| o << maybeNewLine; | |
| } | |
| } | |
| void PrintSExpression::visitElementSegment(ElementSegment* curr) { | |
| bool usesExpressions = TableUtils::usesExpressions(curr, currModule); | |
| auto printElemType = [&]() { | |
| if (!usesExpressions) { | |
| o << "func"; | |
| } else { | |
| printType(curr->type); | |
| } | |
| }; | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "elem "); | |
| curr->name.print(o); | |
| if (curr->isActive()) { | |
| if (usesExpressions || currModule->tables.size() > 1) { | |
| // tableuse | |
| o << " (table "; | |
| curr->table.print(o); | |
| o << ")"; | |
| } | |
| o << ' '; | |
| bool needExplicitOffset = Measurer{}.measure(curr->offset) > 1; | |
| if (needExplicitOffset) { | |
| o << "(offset "; | |
| } | |
| visit(curr->offset); | |
| if (needExplicitOffset) { | |
| o << ')'; | |
| } | |
| if (usesExpressions || currModule->tables.size() > 1) { | |
| o << ' '; | |
| printElemType(); | |
| } | |
| } else { | |
| o << ' '; | |
| printElemType(); | |
| } | |
| if (!usesExpressions) { | |
| for (auto* entry : curr->data) { | |
| auto* refFunc = entry->cast<RefFunc>(); | |
| o << ' '; | |
| refFunc->func.print(o); | |
| } | |
| } else { | |
| for (auto* entry : curr->data) { | |
| o << " (item "; | |
| visit(entry); | |
| o << ')'; | |
| } | |
| } | |
| o << ')' << maybeNewLine; | |
| } | |
| void PrintSExpression::printMemoryHeader(Memory* curr) { | |
| o << '('; | |
| printMedium(o, "memory") << ' '; | |
| curr->name.print(o) << ' '; | |
| if (curr->is64()) { | |
| o << "i64 "; | |
| } | |
| o << curr->initial; | |
| if (curr->hasMax()) { | |
| o << ' ' << curr->max; | |
| } | |
| if (curr->shared) { | |
| printMedium(o, " shared"); | |
| } | |
| if (curr->pageSizeLog2 != Memory::kDefaultPageSizeLog2) { | |
| o << " ("; | |
| printMedium(o, "pagesize") << ' ' << (1 << (curr->pageSizeLog2)); | |
| o << ')'; | |
| } | |
| o << ")"; | |
| } | |
| void PrintSExpression::visitMemory(Memory* curr) { | |
| if (curr->imported()) { | |
| doIndent(o, indent); | |
| o << '('; | |
| emitImportHeader(curr); | |
| printMemoryHeader(curr); | |
| o << ')' << maybeNewLine; | |
| } else { | |
| doIndent(o, indent); | |
| printMemoryHeader(curr); | |
| o << '\n'; | |
| } | |
| } | |
| void PrintSExpression::visitDataSegment(DataSegment* curr) { | |
| if (curr->isActive() && !curr->offset) { | |
| // This data segment must have been created from the datacount section but | |
| // not parsed yet. Skip it. | |
| return; | |
| } | |
| doIndent(o, indent); | |
| o << '('; | |
| printMajor(o, "data "); | |
| curr->name.print(o); | |
| o << ' '; | |
| if (curr->isActive()) { | |
| assert(!currModule || currModule->memories.size() > 0); | |
| if (!currModule || curr->memory != currModule->memories[0]->name) { | |
| o << "(memory "; | |
| curr->memory.print(o); | |
| o << ") "; | |
| } | |
| bool needExplicitOffset = Measurer{}.measure(curr->offset) > 1; | |
| if (needExplicitOffset) { | |
| o << "(offset "; | |
| } | |
| visit(curr->offset); | |
| if (needExplicitOffset) { | |
| o << ")"; | |
| } | |
| o << ' '; | |
| } | |
| String::printEscaped(o, {curr->data.data(), curr->data.size()}); | |
| o << ')' << maybeNewLine; | |
| } | |
| void PrintSExpression::printDylinkSection( | |
| const std::unique_ptr<DylinkSection>& dylinkSection) { | |
| doIndent(o, indent) << ";; dylink section\n"; | |
| doIndent(o, indent) << ";; memorysize: " << dylinkSection->memorySize | |
| << '\n'; | |
| doIndent(o, indent) << ";; memoryalignment: " | |
| << dylinkSection->memoryAlignment << '\n'; | |
| doIndent(o, indent) << ";; tablesize: " << dylinkSection->tableSize << '\n'; | |
| doIndent(o, indent) << ";; tablealignment: " | |
| << dylinkSection->tableAlignment << '\n'; | |
| for (auto& neededDynlib : dylinkSection->neededDynlibs) { | |
| doIndent(o, indent) << ";; needed dynlib: " << neededDynlib << '\n'; | |
| } | |
| if (dylinkSection->tail.size()) { | |
| doIndent(o, indent) << ";; extra dylink data, size " | |
| << dylinkSection->tail.size() << "\n"; | |
| } | |
| } | |
| void PrintSExpression::visitModule(Module* curr) { | |
| setModule(curr); | |
| o << '('; | |
| printMajor(o, "module"); | |
| if (curr->name.is()) { | |
| o << ' '; | |
| curr->name.print(o); | |
| } | |
| incIndent(); | |
| // Use the same type order as the binary output would even though there is | |
| // no code size benefit in the text format. | |
| std::optional<RecGroup> currGroup; | |
| bool nontrivialGroup = false; | |
| auto finishGroup = [&]() { | |
| if (nontrivialGroup) { | |
| decIndent(); | |
| o << maybeNewLine; | |
| } | |
| }; | |
| for (auto type : heapTypes) { | |
| RecGroup newGroup = type.getRecGroup(); | |
| if (!currGroup || *currGroup != newGroup) { | |
| if (currGroup) { | |
| finishGroup(); | |
| } | |
| currGroup = newGroup; | |
| nontrivialGroup = currGroup->size() > 1; | |
| if (nontrivialGroup) { | |
| doIndent(o, indent); | |
| o << "(rec"; | |
| incIndent(); | |
| } | |
| } | |
| doIndent(o, indent); | |
| o << typePrinter(type) << maybeNewLine; | |
| } | |
| finishGroup(); | |
| ModuleUtils::iterImportedMemories( | |
| *curr, [&](Memory* memory) { visitMemory(memory); }); | |
| ModuleUtils::iterImportedTables(*curr, | |
| [&](Table* table) { visitTable(table); }); | |
| ModuleUtils::iterImportedGlobals( | |
| *curr, [&](Global* global) { visitGlobal(global); }); | |
| ModuleUtils::iterImportedFunctions( | |
| *curr, [&](Function* func) { visitFunction(func); }); | |
| ModuleUtils::iterImportedTags(*curr, [&](Tag* tag) { visitTag(tag); }); | |
| ModuleUtils::iterDefinedGlobals(*curr, | |
| [&](Global* global) { visitGlobal(global); }); | |
| ModuleUtils::iterDefinedMemories( | |
| *curr, [&](Memory* memory) { visitMemory(memory); }); | |
| for (auto& segment : curr->dataSegments) { | |
| visitDataSegment(segment.get()); | |
| } | |
| ModuleUtils::iterDefinedTables(*curr, | |
| [&](Table* table) { visitTable(table); }); | |
| for (auto& segment : curr->elementSegments) { | |
| visitElementSegment(segment.get()); | |
| } | |
| auto elemDeclareNames = TableUtils::getFunctionsNeedingElemDeclare(*curr); | |
| if (!elemDeclareNames.empty()) { | |
| doIndent(o, indent); | |
| printMedium(o, "(elem"); | |
| o << " declare func"; | |
| for (auto name : elemDeclareNames) { | |
| o << ' '; | |
| name.print(o); | |
| } | |
| o << ')' << maybeNewLine; | |
| } | |
| ModuleUtils::iterDefinedTags(*curr, [&](Tag* tag) { visitTag(tag); }); | |
| for (auto& child : curr->exports) { | |
| doIndent(o, indent); | |
| visitExport(child.get()); | |
| o << maybeNewLine; | |
| } | |
| if (curr->start.is()) { | |
| doIndent(o, indent); | |
| o << '('; | |
| printMedium(o, "start") << ' '; | |
| curr->start.print(o) << ')'; | |
| o << maybeNewLine; | |
| } | |
| ModuleUtils::iterDefinedFunctions( | |
| *curr, [&](Function* func) { visitFunction(func); }); | |
| if (curr->dylinkSection) { | |
| printDylinkSection(curr->dylinkSection); | |
| } | |
| for (auto& section : curr->customSections) { | |
| doIndent(o, indent); | |
| o << ";; custom section \"" << section.name << "\", size " | |
| << section.data.size(); | |
| bool isPrintable = true; | |
| for (auto c : section.data) { | |
| if (!isprint(static_cast<unsigned char>(c))) { | |
| isPrintable = false; | |
| break; | |
| } | |
| } | |
| if (isPrintable) { | |
| o << ", contents: "; | |
| // std::quoted is not available in all the supported compilers yet. | |
| o << '"'; | |
| for (auto c : section.data) { | |
| if (c == '\\' || c == '"') { | |
| o << '\\'; | |
| } | |
| o << c; | |
| } | |
| o << '"'; | |
| } | |
| o << maybeNewLine; | |
| } | |
| if (curr->hasFeaturesSection) { | |
| doIndent(o, indent); | |
| o << ";; features section: " << curr->features.toString() << '\n'; | |
| } | |
| decIndent(); | |
| o << maybeNewLine; | |
| setModule(nullptr); | |
| } | |
| // Prints out a module | |
| class Printer : public Pass { | |
| protected: | |
| std::ostream& o; | |
| public: | |
| Printer() : o(std::cout) {} | |
| Printer(std::ostream* o) : o(*o) {} | |
| bool modifiesBinaryenIR() override { return false; } | |
| void run(Module* module) override { | |
| PrintSExpression print(o); | |
| print.setDebugInfo(getPassOptions().debugInfo); | |
| print.visitModule(module); | |
| } | |
| }; | |
| Pass* createPrinterPass() { return new Printer(); } | |
| // Prints out a minified module | |
| class MinifiedPrinter : public Printer { | |
| public: | |
| MinifiedPrinter() = default; | |
| MinifiedPrinter(std::ostream* o) : Printer(o) {} | |
| void run(Module* module) override { | |
| PrintSExpression print(o); | |
| print.setMinify(true); | |
| print.setDebugInfo(getPassOptions().debugInfo); | |
| print.visitModule(module); | |
| } | |
| }; | |
| Pass* createMinifiedPrinterPass() { return new MinifiedPrinter(); } | |
| // Prints out a module without elision, i.e., the full ast | |
| class FullPrinter : public Printer { | |
| public: | |
| FullPrinter() = default; | |
| FullPrinter(std::ostream* o) : Printer(o) {} | |
| void run(Module* module) override { | |
| PrintSExpression print(o); | |
| print.setFull(true); | |
| print.setDebugInfo(getPassOptions().debugInfo); | |
| print.currModule = module; | |
| print.visitModule(module); | |
| } | |
| }; | |
| Pass* createFullPrinterPass() { return new FullPrinter(); } | |
| // Print Stack IR (if present) | |
| class PrintStackIR : public Printer { | |
| public: | |
| PrintStackIR() = default; | |
| PrintStackIR(std::ostream* o) : Printer(o) {} | |
| void run(Module* module) override { | |
| PrintSExpression print(o); | |
| print.setDebugInfo(getPassOptions().debugInfo); | |
| print.currModule = module; | |
| print.generateStackIR(getPassOptions()); | |
| print.visitModule(module); | |
| } | |
| }; | |
| static std::ostream& printExpression(Expression* expression, | |
| std::ostream& o, | |
| bool minify, | |
| bool full, | |
| Module* wasm) { | |
| if (!expression) { | |
| o << "(null expression)"; | |
| return o; | |
| } | |
| PrintSExpression print(o); | |
| print.setMinify(minify); | |
| print.currModule = wasm; | |
| if (full || isFullForced()) { | |
| print.setFull(true); | |
| } | |
| print.visit(expression); | |
| if (full || isFullForced()) { | |
| o << " (; "; | |
| printTypeOrName(expression->type, o, wasm); | |
| o << " ;)"; | |
| } | |
| return o; | |
| } | |
| static std::ostream& | |
| printStackInst(StackInst* inst, std::ostream& o, Function* func) { | |
| PrintSExpression printer(o); | |
| switch (inst->op) { | |
| case StackInst::Basic: | |
| case StackInst::BlockBegin: | |
| case StackInst::IfBegin: | |
| case StackInst::LoopBegin: | |
| case StackInst::TryBegin: { | |
| PrintExpressionContents(printer).visit(inst->origin); | |
| break; | |
| } | |
| case StackInst::BlockEnd: | |
| case StackInst::IfEnd: | |
| case StackInst::LoopEnd: | |
| case StackInst::TryEnd: { | |
| printMedium(o, "end"); | |
| o << " ;; type: "; | |
| printer.printType(inst->type); | |
| break; | |
| } | |
| case StackInst::IfElse: { | |
| printMedium(o, "else"); | |
| break; | |
| } | |
| case StackInst::Catch: { | |
| // Because StackInst does not have info on which catch within a try this | |
| // is, we can't print the tag name. | |
| printMedium(o, "catch"); | |
| break; | |
| } | |
| case StackInst::CatchAll: { | |
| printMedium(o, "catch_all"); | |
| break; | |
| } | |
| case StackInst::Delegate: { | |
| printMedium(o, "delegate "); | |
| inst->origin->cast<Try>()->delegateTarget.print(o); | |
| break; | |
| } | |
| default: | |
| WASM_UNREACHABLE("unexpected op"); | |
| } | |
| return o; | |
| } | |
| static std::ostream& printStackIR(StackIR* ir, PrintSExpression& printer) { | |
| std::ostream& o = printer.o; | |
| size_t indent = printer.currFunction ? 2 : 0; | |
| auto doIndent = [&]() { o << std::string(indent, ' '); }; | |
| int controlFlowDepth = 0; | |
| // Stack to track indices of catches within a try | |
| SmallVector<Index, 4> catchIndexStack; | |
| for (Index i = 0; i < (*ir).size(); i++) { | |
| auto* inst = (*ir)[i]; | |
| if (!inst) { | |
| continue; | |
| } | |
| switch (inst->op) { | |
| case StackInst::Basic: { | |
| // Pop is a pseudo instruction and should not be printed in the stack IR | |
| // format to make it valid wat form. | |
| if (inst->origin->is<Pop>()) { | |
| continue; | |
| } | |
| doIndent(); | |
| PrintExpressionContents(printer).visit(inst->origin); | |
| break; | |
| } | |
| case StackInst::TryBegin: | |
| catchIndexStack.push_back(0); | |
| [[fallthrough]]; | |
| case StackInst::BlockBegin: | |
| case StackInst::IfBegin: | |
| case StackInst::LoopBegin: | |
| case StackInst::TryTableBegin: { | |
| controlFlowDepth++; | |
| doIndent(); | |
| PrintExpressionContents(printer).visit(inst->origin); | |
| indent++; | |
| break; | |
| } | |
| case StackInst::TryEnd: | |
| catchIndexStack.pop_back(); | |
| [[fallthrough]]; | |
| case StackInst::BlockEnd: | |
| case StackInst::IfEnd: | |
| case StackInst::LoopEnd: | |
| case StackInst::TryTableEnd: { | |
| controlFlowDepth--; | |
| indent--; | |
| doIndent(); | |
| printMedium(o, "end"); | |
| break; | |
| } | |
| case StackInst::IfElse: { | |
| indent--; | |
| doIndent(); | |
| printMedium(o, "else"); | |
| indent++; | |
| break; | |
| } | |
| case StackInst::Catch: { | |
| indent--; | |
| doIndent(); | |
| printMedium(o, "catch "); | |
| Try* curr = inst->origin->cast<Try>(); | |
| curr->catchTags[catchIndexStack.back()++].print(o); | |
| indent++; | |
| break; | |
| } | |
| case StackInst::CatchAll: { | |
| indent--; | |
| doIndent(); | |
| printMedium(o, "catch_all"); | |
| indent++; | |
| break; | |
| } | |
| case StackInst::Delegate: { | |
| controlFlowDepth--; | |
| indent--; | |
| doIndent(); | |
| printMedium(o, "delegate "); | |
| Try* curr = inst->origin->cast<Try>(); | |
| if (curr->delegateTarget == DELEGATE_CALLER_TARGET) { | |
| o << controlFlowDepth; | |
| } else { | |
| curr->delegateTarget.print(o); | |
| } | |
| break; | |
| } | |
| default: | |
| WASM_UNREACHABLE("unexpected op"); | |
| } | |
| o << '\n'; | |
| } | |
| assert(controlFlowDepth == 0); | |
| return o; | |
| } | |
| std::ostream& | |
| printStackIR(std::ostream& o, Module* module, const PassOptions& options) { | |
| wasm::PassRunner runner(module, options); | |
| runner.add(std::make_unique<PrintStackIR>(&o)); | |
| runner.run(); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::Module& module) { | |
| wasm::PassRunner runner(&module); | |
| wasm::Printer printer(&o); | |
| // Do not use runner.run(), since that will cause an infinite recursion in | |
| // BINARYEN_PASS_DEBUG=3, which prints modules (using this function) as part | |
| // of running passes. | |
| printer.setPassRunner(&runner); | |
| printer.run(&module); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::Function& func) { | |
| wasm::PrintSExpression print(o); | |
| print.setMinify(false); | |
| print.setDebugInfo(false); | |
| print.visitFunction(&func); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::Expression& expression) { | |
| return wasm::printExpression(&expression, o); | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::Expression* expression) { | |
| return wasm::printExpression(expression, o); | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleExpression pair) { | |
| return wasm::printExpression(pair.second, o, false, false, &pair.first); | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression) { | |
| if (Properties::hasUnwritableTypeImmediate(expression.expr)) { | |
| o << "(; unreachable " << getExpressionName(expression.expr) << " ;)"; | |
| return o; | |
| } | |
| wasm::PrintSExpression printer(o); | |
| printer.setModule(expression.module); | |
| wasm::PrintExpressionContents(printer).visit(expression.expr); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst) { | |
| return wasm::printStackInst(&inst, o); | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair) { | |
| wasm::printTypeOrName(pair.second, o, &pair.first); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, wasm::ModuleHeapType pair) { | |
| if (auto it = pair.first.typeNames.find(pair.second); | |
| it != pair.first.typeNames.end()) { | |
| return o << '$' << it->second.name; | |
| } | |
| if (pair.second.isBasic()) { | |
| return o << pair.second; | |
| } | |
| return o << "(unnamed)"; | |
| } | |
| std::ostream& operator<<(std::ostream& o, | |
| const wasm::ImportNames& importNames) { | |
| return o << importNames.module << "." << importNames.name; | |
| } | |
| std::ostream& operator<<(std::ostream& os, wasm::MemoryOrder mo) { | |
| switch (mo) { | |
| case wasm::MemoryOrder::Unordered: | |
| os << "Unordered"; | |
| break; | |
| case wasm::MemoryOrder::Relaxed: | |
| os << "Relaxed"; | |
| break; | |
| case wasm::MemoryOrder::SeqCst: | |
| os << "SeqCst"; | |
| break; | |
| case wasm::MemoryOrder::AcqRel: | |
| os << "AcqRel"; | |
| break; | |
| } | |
| return os; | |
| } | |
| std::ostream& operator<<(std::ostream& o, const Table& table) { | |
| wasm::PrintSExpression printer(o); | |
| // TODO: printTableHeader should take a const Table* | |
| printer.printTableHeader(const_cast<Table*>(&table)); | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, const Global& global) { | |
| wasm::PrintSExpression printer(o); | |
| // TODO: visitGlobal should take a const Global* | |
| printer.visitGlobal(const_cast<Global*>(&global)); | |
| return o; | |
| } | |
| } // namespace wasm |