blob: a87eae325e0ee5d47f2987993389e72e5585c4fe [file] [log] [blame] [edit]
/*
* 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 <ir/module-utils.h>
#include <pass.h>
#include <pretty_printing.h>
#include <wasm-printing.h>
#include <wasm-stack.h>
#include <wasm.h>
namespace wasm {
namespace {
bool isFullForced() {
if (getenv("BINARYEN_PRINT_FULL")) {
return std::stoi(getenv("BINARYEN_PRINT_FULL")) != 0;
}
return false;
}
std::ostream& printName(Name name, std::ostream& o) {
// we need to quote names if they have tricky chars
if (!name.str || !strpbrk(name.str, "()")) {
o << '$' << name.str;
} else {
o << "\"$" << name.str << '"';
}
return o;
}
static 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 printName(name, o);
}
// Unlike the default format, tuple types in s-expressions should not have
// commas.
struct SExprType {
Type type;
SExprType(Type type) : type(type){};
};
static std::ostream& operator<<(std::ostream& o, const SExprType& localType) {
Type type = localType.type;
if (type.isMulti()) {
const std::vector<Type>& types = type.expand();
o << '(' << types[0];
for (size_t i = 1; i < types.size(); ++i) {
o << ' ' << types[i];
}
o << ')';
return o;
}
o << type;
return o;
}
// Wrapper for printing signature names
struct SigName {
Signature sig;
SigName(Signature sig) : sig(sig) {}
};
std::ostream& operator<<(std::ostream& os, SigName sigName) {
auto printType = [&](Type type) {
if (type == Type::none) {
os << "none";
} else {
const std::vector<Type>& types = type.expand();
for (size_t i = 0; i < types.size(); ++i) {
if (i != 0) {
os << '_';
}
os << types[i];
}
}
};
os << '$';
printType(sigName.sig.params);
os << "_=>_";
printType(sigName.sig.results);
return os;
}
} // 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;
}
// Prints the internal contents of an expression: everything but
// the children.
struct PrintExpressionContents
: public OverriddenVisitor<PrintExpressionContents> {
Function* currFunction = nullptr;
std::ostream& o;
PrintExpressionContents(Function* currFunction, std::ostream& o)
: currFunction(currFunction), o(o) {}
void visitBlock(Block* curr) {
printMedium(o, "block");
if (curr->name.is()) {
o << ' ';
printName(curr->name, o);
}
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
}
void visitIf(If* curr) {
printMedium(o, "if");
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
}
void visitLoop(Loop* curr) {
printMedium(o, "loop");
if (curr->name.is()) {
o << ' ';
printName(curr->name, o);
}
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
}
void visitBreak(Break* curr) {
if (curr->condition) {
printMedium(o, "br_if ");
} else {
printMedium(o, "br ");
}
printName(curr->name, o);
}
void visitSwitch(Switch* curr) {
printMedium(o, "br_table");
for (auto& t : curr->targets) {
o << ' ';
printName(t, o);
}
o << ' ';
printName(curr->default_, o);
}
void visitCall(Call* curr) {
if (curr->isReturn) {
printMedium(o, "return_call ");
} else {
printMedium(o, "call ");
}
printName(curr->target, o);
}
void visitCallIndirect(CallIndirect* curr) {
if (curr->isReturn) {
printMedium(o, "return_call_indirect (type ");
} else {
printMedium(o, "call_indirect (type ");
}
o << SigName(curr->sig) << ')';
}
void visitLocalGet(LocalGet* curr) {
printMedium(o, "local.get ");
printLocal(curr->index, currFunction, o);
}
void visitLocalSet(LocalSet* curr) {
if (curr->isTee()) {
printMedium(o, "local.tee ");
} else {
printMedium(o, "local.set ");
}
printLocal(curr->index, currFunction, o);
}
void visitGlobalGet(GlobalGet* curr) {
printMedium(o, "global.get ");
printName(curr->name, o);
}
void visitGlobalSet(GlobalSet* curr) {
printMedium(o, "global.set ");
printName(curr->name, o);
}
void visitLoad(Load* curr) {
prepareColor(o) << forceConcrete(curr->type);
if (curr->isAtomic) {
o << ".atomic";
}
o << ".load";
if (curr->type != Type::unreachable &&
curr->bytes < curr->type.getByteSize()) {
if (curr->bytes == 1) {
o << '8';
} else if (curr->bytes == 2) {
o << "16";
} else if (curr->bytes == 4) {
o << "32";
} else {
abort();
}
o << (curr->signed_ ? "_s" : "_u");
}
restoreNormalColor(o);
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";
if (curr->bytes < 4 || (curr->valueType == Type::i64 && curr->bytes < 8)) {
if (curr->bytes == 1) {
o << '8';
} else if (curr->bytes == 2) {
o << "16";
} else if (curr->bytes == 4) {
o << "32";
} else {
abort();
}
}
restoreNormalColor(o);
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 visitAtomicRMW(AtomicRMW* curr) {
prepareColor(o);
printRMWSize(o, curr->type, curr->bytes);
switch (curr->op) {
case Add:
o << "add";
break;
case Sub:
o << "sub";
break;
case And:
o << "and";
break;
case Or:
o << "or";
break;
case Xor:
o << "xor";
break;
case Xchg:
o << "xchg";
break;
}
if (curr->type != Type::unreachable &&
curr->bytes != curr->type.getByteSize()) {
o << "_u";
}
restoreNormalColor(o);
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);
if (curr->offset) {
o << " offset=" << curr->offset;
}
}
void visitAtomicWait(AtomicWait* curr) {
prepareColor(o);
o << forceConcrete(curr->expectedType) << ".atomic.wait";
if (curr->offset) {
o << " offset=" << curr->offset;
}
}
void visitAtomicNotify(AtomicNotify* curr) {
printMedium(o, "atomic.notify");
if (curr->offset) {
o << " offset=" << curr->offset;
}
}
void visitAtomicFence(AtomicFence* curr) { printMedium(o, "atomic.fence"); }
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 ExtractLaneVecF32x4:
o << "f32x4.extract_lane";
break;
case ExtractLaneVecF64x2:
o << "f64x2.extract_lane";
break;
}
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 ReplaceLaneVecF32x4:
o << "f32x4.replace_lane";
break;
case ReplaceLaneVecF64x2:
o << "f64x2.replace_lane";
break;
}
o << " " << int(curr->index);
}
void visitSIMDShuffle(SIMDShuffle* curr) {
prepareColor(o);
o << "v8x16.shuffle";
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 QFMAF32x4:
o << "f32x4.qfma";
break;
case QFMSF32x4:
o << "f32x4.qfms";
break;
case QFMAF64x2:
o << "f64x2.qfma";
break;
case QFMSF64x2:
o << "f64x2.qfms";
break;
}
}
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;
}
}
void visitSIMDLoad(SIMDLoad* curr) {
prepareColor(o);
switch (curr->op) {
case LoadSplatVec8x16:
o << "v8x16.load_splat";
break;
case LoadSplatVec16x8:
o << "v16x8.load_splat";
break;
case LoadSplatVec32x4:
o << "v32x4.load_splat";
break;
case LoadSplatVec64x2:
o << "v64x2.load_splat";
break;
case LoadExtSVec8x8ToVecI16x8:
o << "i16x8.load8x8_s";
break;
case LoadExtUVec8x8ToVecI16x8:
o << "i16x8.load8x8_u";
break;
case LoadExtSVec16x4ToVecI32x4:
o << "i32x4.load16x4_s";
break;
case LoadExtUVec16x4ToVecI32x4:
o << "i32x4.load16x4_u";
break;
case LoadExtSVec32x2ToVecI64x2:
o << "i64x2.load32x2_s";
break;
case LoadExtUVec32x2ToVecI64x2:
o << "i64x2.load32x2_u";
break;
}
restoreNormalColor(o);
if (curr->offset) {
o << " offset=" << curr->offset;
}
if (curr->align != curr->getMemBytes()) {
o << " align=" << curr->align;
}
}
void visitMemoryInit(MemoryInit* curr) {
prepareColor(o);
o << "memory.init " << curr->segment;
}
void visitDataDrop(DataDrop* curr) {
prepareColor(o);
o << "data.drop " << curr->segment;
}
void visitMemoryCopy(MemoryCopy* curr) {
prepareColor(o);
o << "memory.copy";
}
void visitMemoryFill(MemoryFill* curr) {
prepareColor(o);
o << "memory.fill";
}
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 SplatVecF32x4:
o << "f32x4.splat";
break;
case SplatVecF64x2:
o << "f64x2.splat";
break;
case NotVec128:
o << "v128.not";
break;
case AbsVecI8x16:
o << "i8x16.abs";
break;
case NegVecI8x16:
o << "i8x16.neg";
break;
case AnyTrueVecI8x16:
o << "i8x16.any_true";
break;
case AllTrueVecI8x16:
o << "i8x16.all_true";
break;
case BitmaskVecI8x16:
o << "i8x16.bitmask";
break;
case AbsVecI16x8:
o << "i16x8.abs";
break;
case NegVecI16x8:
o << "i16x8.neg";
break;
case AnyTrueVecI16x8:
o << "i16x8.any_true";
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 AnyTrueVecI32x4:
o << "i32x4.any_true";
break;
case AllTrueVecI32x4:
o << "i32x4.all_true";
break;
case BitmaskVecI32x4:
o << "i32x4.bitmask";
break;
case NegVecI64x2:
o << "i64x2.neg";
break;
case AnyTrueVecI64x2:
o << "i64x2.any_true";
break;
case AllTrueVecI64x2:
o << "i64x2.all_true";
break;
case AbsVecF32x4:
o << "f32x4.abs";
break;
case NegVecF32x4:
o << "f32x4.neg";
break;
case SqrtVecF32x4:
o << "f32x4.sqrt";
break;
case AbsVecF64x2:
o << "f64x2.abs";
break;
case NegVecF64x2:
o << "f64x2.neg";
break;
case SqrtVecF64x2:
o << "f64x2.sqrt";
break;
case TruncSatSVecF32x4ToVecI32x4:
o << "i32x4.trunc_sat_f32x4_s";
break;
case TruncSatUVecF32x4ToVecI32x4:
o << "i32x4.trunc_sat_f32x4_u";
break;
case TruncSatSVecF64x2ToVecI64x2:
o << "i64x2.trunc_sat_f64x2_s";
break;
case TruncSatUVecF64x2ToVecI64x2:
o << "i64x2.trunc_sat_f64x2_u";
break;
case ConvertSVecI32x4ToVecF32x4:
o << "f32x4.convert_i32x4_s";
break;
case ConvertUVecI32x4ToVecF32x4:
o << "f32x4.convert_i32x4_u";
break;
case ConvertSVecI64x2ToVecF64x2:
o << "f64x2.convert_i64x2_s";
break;
case ConvertUVecI64x2ToVecF64x2:
o << "f64x2.convert_i64x2_u";
break;
case WidenLowSVecI8x16ToVecI16x8:
o << "i16x8.widen_low_i8x16_s";
break;
case WidenHighSVecI8x16ToVecI16x8:
o << "i16x8.widen_high_i8x16_s";
break;
case WidenLowUVecI8x16ToVecI16x8:
o << "i16x8.widen_low_i8x16_u";
break;
case WidenHighUVecI8x16ToVecI16x8:
o << "i16x8.widen_high_i8x16_u";
break;
case WidenLowSVecI16x8ToVecI32x4:
o << "i32x4.widen_low_i16x8_s";
break;
case WidenHighSVecI16x8ToVecI32x4:
o << "i32x4.widen_high_i16x8_s";
break;
case WidenLowUVecI16x8ToVecI32x4:
o << "i32x4.widen_low_i16x8_u";
break;
case WidenHighUVecI16x8ToVecI32x4:
o << "i32x4.widen_high_i16x8_u";
break;
case InvalidUnary:
WASM_UNREACHABLE("unvalid unary operator");
}
}
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 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_saturate_s";
break;
case AddSatUVecI8x16:
o << "i8x16.add_saturate_u";
break;
case SubVecI8x16:
o << "i8x16.sub";
break;
case SubSatSVecI8x16:
o << "i8x16.sub_saturate_s";
break;
case SubSatUVecI8x16:
o << "i8x16.sub_saturate_u";
break;
case MulVecI8x16:
o << "i8x16.mul";
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_saturate_s";
break;
case AddSatUVecI16x8:
o << "i16x8.add_saturate_u";
break;
case SubVecI16x8:
o << "i16x8.sub";
break;
case SubSatSVecI16x8:
o << "i16x8.sub_saturate_s";
break;
case SubSatUVecI16x8:
o << "i16x8.sub_saturate_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 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 AddVecI64x2:
o << "i64x2.add";
break;
case SubVecI64x2:
o << "i64x2.sub";
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 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 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 SwizzleVec8x16:
o << "v8x16.swizzle";
break;
case InvalidBinary:
WASM_UNREACHABLE("unvalid binary operator");
}
restoreNormalColor(o);
}
void visitSelect(Select* curr) {
prepareColor(o) << "select";
if (curr->type.isRef()) {
o << " (result " << curr->type << ')';
}
}
void visitDrop(Drop* curr) { printMedium(o, "drop"); }
void visitReturn(Return* curr) { printMedium(o, "return"); }
void visitHost(Host* curr) {
switch (curr->op) {
case MemorySize:
printMedium(o, "memory.size");
break;
case MemoryGrow:
printMedium(o, "memory.grow");
break;
}
}
void visitRefNull(RefNull* curr) { printMedium(o, "ref.null"); }
void visitRefIsNull(RefIsNull* curr) { printMedium(o, "ref.is_null"); }
void visitRefFunc(RefFunc* curr) {
printMedium(o, "ref.func ");
printName(curr->func, o);
}
void visitTry(Try* curr) {
printMedium(o, "try");
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
}
void visitThrow(Throw* curr) {
printMedium(o, "throw ");
printName(curr->event, o);
}
void visitRethrow(Rethrow* curr) { printMedium(o, "rethrow"); }
void visitBrOnExn(BrOnExn* curr) {
printMedium(o, "br_on_exn ");
printName(curr->name, o);
o << " ";
printName(curr->event, o);
}
void visitNop(Nop* curr) { printMinor(o, "nop"); }
void visitUnreachable(Unreachable* curr) { printMinor(o, "unreachable"); }
void visitPush(Push* curr) { prepareColor(o) << "push"; }
void visitPop(Pop* curr) {
prepareColor(o) << curr->type;
o << ".pop";
restoreNormalColor(o);
}
void visitTupleMake(TupleMake* curr) { printMedium(o, "tuple.make"); }
void visitTupleExtract(TupleExtract* curr) {
printMedium(o, "tuple.extract ");
o << curr->index;
}
};
// Prints an expression in s-expr format, including both the
// internal contents and the nested children.
struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
std::ostream& o;
unsigned indent = 0;
bool minify;
const char* maybeSpace;
const char* maybeNewLine;
bool full = false; // whether to not elide nodes in output when possible
// (like implicit blocks) and to emit types
bool printStackIR = false; // whether to print stack IR if it is present
// (if false, and Stack IR is there, we just
// note it exists)
Module* currModule = nullptr;
Function* currFunction = nullptr;
Function::DebugLocation lastPrintedLocation;
bool debugInfo;
PrintSExpression(std::ostream& o) : o(o) {
setMinify(false);
if (!full) {
full = isFullForced();
}
}
void printDebugLocation(const Function::DebugLocation& location) {
if (lastPrintedLocation == location) {
return;
}
lastPrintedLocation = location;
auto fileName = currModule->debugInfoFileNames[location.fileIndex];
o << ";;@ " << fileName << ":" << location.lineNumber << ":"
<< location.columnNumber << '\n';
doIndent(o, indent);
}
void printDebugLocation(Expression* curr) {
if (currFunction) {
// show an annotation, if there is one
auto& debugLocations = currFunction->debugLocations;
auto iter = debugLocations.find(curr);
if (iter != debugLocations.end()) {
printDebugLocation(iter->second);
}
// show a binary position, if there is one
if (debugInfo) {
auto iter = currFunction->expressionLocations.find(curr);
if (iter != currFunction->expressionLocations.end()) {
Colors::grey(o);
o << ";; code offset: 0x" << std::hex << iter->second.start
<< std::dec << '\n';
restoreNormalColor(o);
doIndent(o, indent);
}
}
}
}
void visit(Expression* curr) {
printDebugLocation(curr);
OverriddenVisitor<PrintSExpression>::visit(curr);
}
void setMinify(bool minify_) {
minify = minify_;
maybeSpace = minify ? "" : " ";
maybeNewLine = minify ? "" : "\n";
}
void setFull(bool full_) { full = full_; }
void setPrintStackIR(bool printStackIR_) { printStackIR = printStackIR_; }
void setDebugInfo(bool debugInfo_) { debugInfo = debugInfo_; }
void incIndent() {
if (minify) {
return;
}
o << '\n';
indent++;
}
void decIndent() {
if (!minify) {
assert(indent > 0);
indent--;
doIndent(o, indent);
}
o << ')';
}
void printFullLine(Expression* expression) {
!minify && doIndent(o, indent);
if (full) {
o << "[" << expression->type << "] ";
}
visit(expression);
o << maybeNewLine;
}
// loop, if, and try can contain implicit blocks. But they are not needed to
// be printed in some cases.
void maybePrintImplicitBlock(Expression* curr, bool allowMultipleInsts) {
auto block = curr->dynCast<Block>();
if (!full && block && block->name.isNull() &&
(allowMultipleInsts || block->list.size() == 1)) {
for (auto expression : block->list) {
printFullLine(expression);
}
} else {
printFullLine(curr);
}
}
void 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);
printDebugLocation(curr);
}
stack.push_back(curr);
if (full) {
o << "[" << curr->type << "] ";
}
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
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
}
}
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]);
}
}
decIndent();
if (full) {
o << " ;; end block";
if (curr->name.is()) {
o << ' ' << curr->name;
}
}
}
void visitIf(If* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->condition);
maybePrintImplicitBlock(curr->ifTrue, false);
if (curr->ifFalse) {
maybePrintImplicitBlock(curr->ifFalse, false);
}
decIndent();
if (full) {
o << " ;; end if";
}
}
void visitLoop(Loop* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
maybePrintImplicitBlock(curr->body, true);
decIndent();
if (full) {
o << " ;; end loop";
if (curr->name.is()) {
o << ' ' << curr->name;
}
}
}
void visitBreak(Break* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
if (curr->condition) {
incIndent();
} else {
if (!curr->value || curr->value->is<Nop>()) {
// avoid a new line just for the parens
o << ')';
return;
}
incIndent();
}
if (curr->value && !curr->value->is<Nop>()) {
printFullLine(curr->value);
}
if (curr->condition) {
printFullLine(curr->condition);
}
decIndent();
}
void visitSwitch(Switch* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
if (curr->value && !curr->value->is<Nop>()) {
printFullLine(curr->value);
}
printFullLine(curr->condition);
decIndent();
}
template<typename CallBase> void printCallOperands(CallBase* curr) {
if (curr->operands.size() > 0) {
incIndent();
for (auto operand : curr->operands) {
printFullLine(operand);
}
decIndent();
} else {
o << ')';
}
}
void visitCall(Call* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
printCallOperands(curr);
}
void visitCallIndirect(CallIndirect* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
for (auto operand : curr->operands) {
printFullLine(operand);
}
printFullLine(curr->target);
decIndent();
}
void visitLocalGet(LocalGet* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitLocalSet(LocalSet* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitGlobalGet(GlobalGet* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitGlobalSet(GlobalSet* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitLoad(Load* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
decIndent();
}
void visitStore(Store* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
printFullLine(curr->value);
decIndent();
}
void visitAtomicRMW(AtomicRMW* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
printFullLine(curr->value);
decIndent();
}
void visitAtomicCmpxchg(AtomicCmpxchg* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
printFullLine(curr->expected);
printFullLine(curr->replacement);
decIndent();
}
void visitAtomicWait(AtomicWait* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
restoreNormalColor(o);
incIndent();
printFullLine(curr->ptr);
printFullLine(curr->expected);
printFullLine(curr->timeout);
decIndent();
}
void visitAtomicNotify(AtomicNotify* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
printFullLine(curr->notifyCount);
decIndent();
}
void visitAtomicFence(AtomicFence* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitSIMDExtract(SIMDExtract* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->vec);
decIndent();
}
void visitSIMDReplace(SIMDReplace* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->vec);
printFullLine(curr->value);
decIndent();
}
void visitSIMDShuffle(SIMDShuffle* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->left);
printFullLine(curr->right);
decIndent();
}
void visitSIMDTernary(SIMDTernary* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->a);
printFullLine(curr->b);
printFullLine(curr->c);
decIndent();
}
void visitSIMDShift(SIMDShift* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->vec);
printFullLine(curr->shift);
decIndent();
}
void visitSIMDLoad(SIMDLoad* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
decIndent();
}
void visitMemoryInit(MemoryInit* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->dest);
printFullLine(curr->offset);
printFullLine(curr->size);
decIndent();
}
void visitDataDrop(DataDrop* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitMemoryCopy(MemoryCopy* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->dest);
printFullLine(curr->source);
printFullLine(curr->size);
decIndent();
}
void visitMemoryFill(MemoryFill* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->dest);
printFullLine(curr->value);
printFullLine(curr->size);
decIndent();
}
void visitConst(Const* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitUnary(Unary* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitBinary(Binary* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->left);
printFullLine(curr->right);
decIndent();
}
void visitSelect(Select* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ifTrue);
printFullLine(curr->ifFalse);
printFullLine(curr->condition);
decIndent();
}
void visitDrop(Drop* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitReturn(Return* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
if (!curr->value) {
// avoid a new line just for the parens
o << ')';
return;
}
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitHost(Host* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
switch (curr->op) {
case MemoryGrow: {
incIndent();
printFullLine(curr->operands[0]);
decIndent();
break;
}
case MemorySize: {
o << ')';
}
}
}
void visitRefNull(RefNull* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitRefIsNull(RefIsNull* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitRefFunc(RefFunc* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
// try-catch-end is written in the folded wat format as
// (try
// ...
// (catch
// ...
// )
// )
// The parenthesis wrapping 'catch' is just a syntax and does not affect
// nested depths of instructions within.
void visitTry(Try* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
maybePrintImplicitBlock(curr->body, false);
doIndent(o, indent);
o << "(catch";
incIndent();
maybePrintImplicitBlock(curr->catchBody, true);
decIndent();
o << "\n";
decIndent();
if (full) {
o << " ;; end try";
}
}
void visitThrow(Throw* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
for (auto operand : curr->operands) {
printFullLine(operand);
}
decIndent();
}
void visitRethrow(Rethrow* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->exnref);
decIndent();
}
void visitBrOnExn(BrOnExn* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->exnref);
decIndent();
}
void visitNop(Nop* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitUnreachable(Unreachable* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitPush(Push* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->value);
decIndent();
}
void visitPop(Pop* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
void visitTupleMake(TupleMake* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
for (auto operand : curr->operands) {
printFullLine(operand);
}
decIndent();
}
void visitTupleExtract(TupleExtract* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->tuple);
decIndent();
}
// Module-level visitors
void handleSignature(Signature curr, Name* funcName = nullptr) {
o << "(func";
if (funcName) {
o << " $" << *funcName;
}
if (curr.params.size() > 0) {
o << maybeSpace;
o << ParamType(curr.params);
}
if (curr.results.size() > 0) {
o << maybeSpace;
o << ResultType(curr.results);
}
o << ")";
}
void visitExport(Export* curr) {
o << '(';
printMedium(o, "export ");
printText(o, curr->name.str) << " (";
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::Event:
o << "event";
break;
case ExternalKind::Invalid:
WASM_UNREACHABLE("invalid ExternalKind");
}
o << ' ';
printName(curr->value, o) << "))";
}
void emitImportHeader(Importable* curr) {
printMedium(o, "import ");
printText(o, curr->module.str) << ' ';
printText(o, curr->base.str) << ' ';
}
void visitGlobal(Global* curr) {
if (curr->imported()) {
visitImportedGlobal(curr);
} else {
visitDefinedGlobal(curr);
}
}
void emitGlobalType(Global* curr) {
if (curr->mutable_) {
o << "(mut " << SExprType(curr->type) << ')';
} else {
o << SExprType(curr->type);
}
}
void visitImportedGlobal(Global* curr) {
doIndent(o, indent);
o << '(';
emitImportHeader(curr);
o << "(global ";
printName(curr->name, o) << ' ';
emitGlobalType(curr);
o << "))" << maybeNewLine;
}
void visitDefinedGlobal(Global* curr) {
doIndent(o, indent);
o << '(';
printMedium(o, "global ");
printName(curr->name, o) << ' ';
emitGlobalType(curr);
o << ' ';
visit(curr->init);
o << ')';
o << maybeNewLine;
}
void visitFunction(Function* curr) {
if (curr->imported()) {
visitImportedFunction(curr);
} else {
visitDefinedFunction(curr);
}
}
void visitImportedFunction(Function* curr) {
doIndent(o, indent);
currFunction = curr;
lastPrintedLocation = {0, 0, 0};
o << '(';
emitImportHeader(curr);
handleSignature(curr->sig, &curr->name);
o << ')';
o << maybeNewLine;
}
void visitDefinedFunction(Function* curr) {
doIndent(o, indent);
currFunction = curr;
lastPrintedLocation = {0, 0, 0};
if (currFunction->prologLocation.size()) {
printDebugLocation(*currFunction->prologLocation.begin());
}
o << '(';
printMajor(o, "func ");
printName(curr->name, o);
if (!printStackIR && curr->stackIR && !minify) {
o << " (; has Stack IR ;)";
}
const std::vector<Type>& params = curr->sig.params.expand();
if (params.size() > 0) {
for (size_t i = 0; i < params.size(); i++) {
o << maybeSpace;
o << '(';
printMinor(o, "param ");
printLocal(i, currFunction, o);
o << ' ' << params[i] << ')';
}
}
if (curr->sig.results != Type::none) {
o << maybeSpace;
o << ResultType(curr->sig.results);
}
incIndent();
for (size_t i = curr->getVarIndexBase(); i < curr->getNumLocals(); i++) {
doIndent(o, indent);
o << '(';
printMinor(o, "local ");
printLocal(i, currFunction, o)
<< ' ' << SExprType(curr->getLocalType(i)) << ')';
o << maybeNewLine;
}
// Print the body.
if (!printStackIR || !curr->stackIR) {
// 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);
}
} else {
// Print the stack IR.
WasmPrinter::printStackIR(curr->stackIR.get(), o, curr);
}
if (currFunction->epilogLocation.size() &&
lastPrintedLocation != *currFunction->epilogLocation.begin()) {
// Print last debug location: mix of decIndent and printDebugLocation
// logic.
doIndent(o, indent);
if (!minify) {
indent--;
}
printDebugLocation(*currFunction->epilogLocation.begin());
o << ')';
} else {
decIndent();
}
o << maybeNewLine;
}
void visitEvent(Event* curr) {
if (curr->imported()) {
visitImportedEvent(curr);
} else {
visitDefinedEvent(curr);
}
}
void visitImportedEvent(Event* curr) {
doIndent(o, indent);
o << '(';
emitImportHeader(curr);
o << "(event ";
printName(curr->name, o);
o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace;
o << ParamType(curr->sig.params);
o << "))";
o << maybeNewLine;
}
void visitDefinedEvent(Event* curr) {
doIndent(o, indent);
o << '(';
printMedium(o, "event ");
printName(curr->name, o);
o << maybeSpace << "(attr " << curr->attribute << ')' << maybeSpace;
o << ParamType(curr->sig.params);
o << ")" << maybeNewLine;
}
void printTableHeader(Table* curr) {
o << '(';
printMedium(o, "table") << ' ';
printName(curr->name, o) << ' ';
o << curr->initial;
if (curr->hasMax()) {
o << ' ' << curr->max;
}
o << " funcref)";
}
void visitTable(Table* curr) {
if (!curr->exists) {
return;
}
if (curr->imported()) {
doIndent(o, indent);
o << '(';
emitImportHeader(curr);
printTableHeader(&currModule->table);
o << ')' << maybeNewLine;
} else {
doIndent(o, indent);
printTableHeader(curr);
o << maybeNewLine;
}
for (auto& segment : curr->segments) {
// Don't print empty segments
if (segment.data.empty()) {
continue;
}
doIndent(o, indent);
o << '(';
printMajor(o, "elem ");
visit(segment.offset);
for (auto name : segment.data) {
o << ' ';
printName(name, o);
}
o << ')' << maybeNewLine;
}
}
void printMemoryHeader(Memory* curr) {
o << '(';
printMedium(o, "memory") << ' ';
printName(curr->name, o) << ' ';
if (curr->shared) {
o << '(';
printMedium(o, "shared ");
}
o << curr->initial;
if (curr->hasMax()) {
o << ' ' << curr->max;
}
if (curr->shared) {
o << ")";
}
o << ")";
}
void visitMemory(Memory* curr) {
if (!curr->exists) {
return;
}
if (curr->imported()) {
doIndent(o, indent);
o << '(';
emitImportHeader(curr);
printMemoryHeader(&currModule->memory);
o << ')' << maybeNewLine;
} else {
doIndent(o, indent);
printMemoryHeader(curr);
o << '\n';
}
for (auto segment : curr->segments) {
doIndent(o, indent);
o << '(';
printMajor(o, "data ");
if (segment.isPassive) {
printMedium(o, "passive");
} else {
visit(segment.offset);
}
o << " \"";
for (size_t i = 0; i < segment.data.size(); i++) {
unsigned char c = segment.data[i];
switch (c) {
case '\n':
o << "\\n";
break;
case '\r':
o << "\\0d";
break;
case '\t':
o << "\\t";
break;
case '\f':
o << "\\0c";
break;
case '\b':
o << "\\08";
break;
case '\\':
o << "\\\\";
break;
case '"':
o << "\\\"";
break;
case '\'':
o << "\\'";
break;
default: {
if (c >= 32 && c < 127) {
o << c;
} else {
o << std::hex << '\\' << (c / 16) << (c % 16) << std::dec;
}
}
}
}
o << "\")" << maybeNewLine;
}
}
void 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';
}
}
void visitModule(Module* curr) {
currModule = curr;
o << '(';
printMajor(o, "module");
incIndent();
std::vector<Signature> signatures;
std::unordered_map<Signature, Index> indices;
ModuleUtils::collectSignatures(*curr, signatures, indices);
for (auto sig : signatures) {
doIndent(o, indent);
o << '(';
printMedium(o, "type") << ' ';
o << SigName(sig) << ' ';
handleSignature(sig);
o << ")" << maybeNewLine;
}
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::iterImportedEvents(*curr,
[&](Event* event) { visitEvent(event); });
ModuleUtils::iterDefinedMemories(
*curr, [&](Memory* memory) { visitMemory(memory); });
ModuleUtils::iterDefinedTables(*curr,
[&](Table* table) { visitTable(table); });
ModuleUtils::iterDefinedGlobals(
*curr, [&](Global* global) { visitGlobal(global); });
ModuleUtils::iterDefinedEvents(*curr,
[&](Event* event) { visitEvent(event); });
for (auto& child : curr->exports) {
doIndent(o, indent);
visitExport(child.get());
o << maybeNewLine;
}
if (curr->start.is()) {
doIndent(o, indent);
o << '(';
printMedium(o, "start") << ' ';
printName(curr->start, o) << ')';
o << maybeNewLine;
}
ModuleUtils::iterDefinedFunctions(
*curr, [&](Function* func) { visitFunction(func); });
if (curr->dylinkSection) {
printDylinkSection(curr->dylinkSection);
}
for (auto& section : curr->userSections) {
doIndent(o, indent);
o << ";; custom section \"" << section.name << "\", size "
<< section.data.size();
bool isPrintable = true;
for (auto c : section.data) {
if (!isprint(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;
}
decIndent();
o << maybeNewLine;
currModule = 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(PassRunner* runner, Module* module) override {
PrintSExpression print(o);
print.setDebugInfo(runner->options.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(PassRunner* runner, Module* module) override {
PrintSExpression print(o);
print.setMinify(true);
print.setDebugInfo(runner->options.debugInfo);
print.visitModule(module);
}
};
Pass* createMinifiedPrinterPass() { return new MinifiedPrinter(); }
// Prints out a module withough elision, i.e., the full ast
class FullPrinter : public Printer {
public:
FullPrinter() = default;
FullPrinter(std::ostream* o) : Printer(o) {}
void run(PassRunner* runner, Module* module) override {
PrintSExpression print(o);
print.setFull(true);
print.setDebugInfo(runner->options.debugInfo);
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(PassRunner* runner, Module* module) override {
PrintSExpression print(o);
print.setDebugInfo(runner->options.debugInfo);
print.setPrintStackIR(true);
print.visitModule(module);
}
};
Pass* createPrintStackIRPass() { return new PrintStackIR(); }
// Print individual expressions
std::ostream& WasmPrinter::printModule(Module* module, std::ostream& o) {
PassRunner runner(module);
Printer(&o).run(&runner, module);
return o;
}
std::ostream& WasmPrinter::printModule(Module* module) {
return printModule(module, std::cout);
}
std::ostream& WasmPrinter::printExpression(Expression* expression,
std::ostream& o,
bool minify,
bool full) {
if (!expression) {
o << "(null expression)";
return o;
}
PrintSExpression print(o);
print.setMinify(minify);
if (full || isFullForced()) {
print.setFull(true);
o << "[" << expression->type << "] ";
}
print.visit(expression);
return o;
}
std::ostream&
WasmPrinter::printStackInst(StackInst* inst, std::ostream& o, Function* func) {
switch (inst->op) {
case StackInst::Basic: {
PrintExpressionContents(func, o).visit(inst->origin);
break;
}
case StackInst::BlockBegin:
case StackInst::IfBegin:
case StackInst::LoopBegin:
case StackInst::TryBegin: {
o << getExpressionName(inst->origin);
break;
}
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd:
case StackInst::TryEnd: {
o << "end (" << inst->type << ')';
break;
}
case StackInst::IfElse: {
o << "else";
break;
}
case StackInst::Catch: {
o << "catch";
break;
}
default:
WASM_UNREACHABLE("unexpeted op");
}
return o;
}
std::ostream&
WasmPrinter::printStackIR(StackIR* ir, std::ostream& o, Function* func) {
size_t indent = func ? 2 : 0;
auto doIndent = [&indent, &o]() {
for (size_t j = 0; j < indent; j++) {
o << ' ';
}
};
for (Index i = 0; i < (*ir).size(); i++) {
auto* inst = (*ir)[i];
if (!inst) {
continue;
}
switch (inst->op) {
case StackInst::Basic: {
doIndent();
// push and pop are pseudo instructions and should not be printed in the
// stack IR format to make it valid wat form.
if (inst->origin->is<Push>() || inst->origin->is<Pop>()) {
break;
}
PrintExpressionContents(func, o).visit(inst->origin);
break;
}
case StackInst::BlockBegin:
case StackInst::IfBegin:
case StackInst::LoopBegin:
case StackInst::TryBegin: {
doIndent();
PrintExpressionContents(func, o).visit(inst->origin);
indent++;
break;
}
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd:
case StackInst::TryEnd: {
indent--;
doIndent();
o << "end";
break;
}
case StackInst::IfElse: {
indent--;
doIndent();
o << "else";
indent++;
break;
}
case StackInst::Catch: {
indent--;
doIndent();
o << "catch";
indent++;
break;
}
default:
WASM_UNREACHABLE("unexpeted op");
}
std::cout << '\n';
}
return o;
}
} // namespace wasm