summaryrefslogtreecommitdiffstats
path: root/third_party/wasm2c/src/decompiler.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/wasm2c/src/decompiler.cc
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/wasm2c/src/decompiler.cc')
-rw-r--r--third_party/wasm2c/src/decompiler.cc857
1 files changed, 857 insertions, 0 deletions
diff --git a/third_party/wasm2c/src/decompiler.cc b/third_party/wasm2c/src/decompiler.cc
new file mode 100644
index 0000000000..dd8eb76701
--- /dev/null
+++ b/third_party/wasm2c/src/decompiler.cc
@@ -0,0 +1,857 @@
+/*
+ * Copyright 2019 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wabt/decompiler.h"
+
+#include "wabt/decompiler-ast.h"
+#include "wabt/decompiler-ls.h"
+#include "wabt/decompiler-naming.h"
+
+#include "wabt/stream.h"
+
+#define WABT_TRACING 0
+#include "wabt/tracing.h"
+
+#include <inttypes.h>
+
+namespace wabt {
+
+struct Decompiler {
+ Decompiler(const Module& module, const DecompileOptions& options)
+ : mc(module), options(options) {}
+
+ // Sorted such that "greater precedence" is also the bigger enum val.
+ enum Precedence {
+ // Low precedence.
+ None, // precedence doesn't matter, since never nested.
+ Assign, // =
+ OtherBin, // min
+ Bit, // & |
+ Equal, // == != < > >= <=
+ Shift, // << >>
+ Add, // + -
+ Multiply, // * / %
+ If, // if{}
+ Indexing, // []
+ Atomic, // (), a, 1, a()
+ // High precedence.
+ };
+
+ // Anything besides these will get parentheses if used with equal precedence,
+ // for clarity.
+ bool Associative(Precedence p) {
+ return p == Precedence::Add || p == Precedence::Multiply;
+ }
+
+ struct Value {
+ std::vector<std::string> v;
+ // Lazily add bracketing only if the parent requires it.
+ // This is the precedence level of this value, for example, if this
+ // precedence is Add, and the parent is Multiply, bracketing is needed,
+ // but not if it is the reverse.
+ Precedence precedence;
+
+ Value(std::vector<std::string>&& v, Precedence p) : v(v), precedence(p) {}
+
+ size_t width() {
+ size_t w = 0;
+ for (auto& s : v) {
+ w = std::max(w, s.size());
+ }
+ return w;
+ }
+
+ // This value should really never be copied, only moved.
+ Value(Value&& rhs) = default;
+ Value(const Value& rhs) = delete;
+ Value& operator=(Value&& rhs) = default;
+ Value& operator=(const Value& rhs) = delete;
+ };
+
+ std::string to_string(double d) {
+ auto s = std::to_string(d);
+ // Remove redundant trailing '0's that to_string produces.
+ while (s.size() > 2 && s.back() == '0' && s[s.size() - 2] != '.')
+ s.pop_back();
+ return s;
+ }
+
+ std::string Indent(size_t amount) { return std::string(amount, ' '); }
+
+ std::string OpcodeToToken(Opcode opcode) {
+ std::string s = opcode.GetDecomp();
+ std::replace(s.begin(), s.end(), '.', '_');
+ return s;
+ }
+
+ void IndentValue(Value& val, size_t amount, std::string_view first_indent) {
+ auto indent = Indent(amount);
+ for (auto& stat : val.v) {
+ auto is = (&stat != &val.v[0] || first_indent.empty())
+ ? std::string_view(indent)
+ : first_indent;
+ stat.insert(0, is.data(), is.size());
+ }
+ }
+
+ Value WrapChild(Value& child,
+ std::string_view prefix,
+ std::string_view postfix,
+ Precedence precedence) {
+ auto width = prefix.size() + postfix.size() + child.width();
+ auto& v = child.v;
+ if (width < target_exp_width ||
+ (prefix.size() <= indent_amount && postfix.size() <= indent_amount)) {
+ if (v.size() == 1) {
+ // Fits in a single line.
+ v[0].insert(0, prefix.data(), prefix.size());
+ v[0].append(postfix.data(), postfix.size());
+ } else {
+ // Multiline, but with prefix on same line.
+ IndentValue(child, prefix.size(), prefix);
+ v.back().append(postfix.data(), postfix.size());
+ }
+ } else {
+ // Multiline with prefix on its own line.
+ IndentValue(child, indent_amount, {});
+ v.insert(v.begin(), std::string(prefix));
+ v.back().append(postfix.data(), postfix.size());
+ }
+ child.precedence = precedence;
+ return std::move(child);
+ }
+
+ void BracketIfNeeded(Value& val, Precedence parent_precedence) {
+ if (parent_precedence < val.precedence ||
+ (parent_precedence == val.precedence &&
+ Associative(parent_precedence))) {
+ return;
+ }
+ val = WrapChild(val, "(", ")", Precedence::Atomic);
+ }
+
+ Value WrapBinary(std::vector<Value>& args,
+ std::string_view infix,
+ bool indent_right,
+ Precedence precedence) {
+ assert(args.size() == 2);
+ auto& left = args[0];
+ auto& right = args[1];
+ BracketIfNeeded(left, precedence);
+ BracketIfNeeded(right, precedence);
+ auto width = infix.size() + left.width() + right.width() + 2;
+ if (width < target_exp_width && left.v.size() == 1 && right.v.size() == 1) {
+ return Value{{cat(left.v[0], " ", infix, " ", right.v[0])}, precedence};
+ } else {
+ Value bin{{}, precedence};
+ std::move(left.v.begin(), left.v.end(), std::back_inserter(bin.v));
+ bin.v.back().append(" ", 1);
+ bin.v.back().append(infix.data(), infix.size());
+ if (indent_right) {
+ IndentValue(right, indent_amount, {});
+ }
+ std::move(right.v.begin(), right.v.end(), std::back_inserter(bin.v));
+ return bin;
+ }
+ }
+
+ Value WrapNAry(std::vector<Value>& args,
+ std::string_view prefix,
+ std::string_view postfix,
+ Precedence precedence) {
+ size_t total_width = 0;
+ size_t max_width = 0;
+ bool multiline = false;
+ for (auto& child : args) {
+ auto w = child.width();
+ max_width = std::max(max_width, w);
+ total_width += w;
+ multiline = multiline || child.v.size() > 1;
+ }
+ if (!multiline &&
+ (total_width + prefix.size() + postfix.size() < target_exp_width ||
+ args.empty())) {
+ // Single line.
+ auto s = std::string(prefix);
+ for (auto& child : args) {
+ if (&child != &args[0])
+ s += ", ";
+ s += child.v[0];
+ }
+ s += postfix;
+ return Value{{std::move(s)}, precedence};
+ } else {
+ // Multi-line.
+ Value ml{{}, precedence};
+ auto ident_with_name = max_width + prefix.size() < target_exp_width;
+ size_t i = 0;
+ for (auto& child : args) {
+ IndentValue(child, ident_with_name ? prefix.size() : indent_amount,
+ !i && ident_with_name ? prefix : std::string_view{});
+ if (i < args.size() - 1) {
+ child.v.back() += ",";
+ }
+ std::move(child.v.begin(), child.v.end(), std::back_inserter(ml.v));
+ i++;
+ }
+ if (!ident_with_name) {
+ ml.v.insert(ml.v.begin(), std::string(prefix));
+ }
+ ml.v.back() += postfix;
+ return ml;
+ }
+ }
+
+ std::string_view VarName(std::string_view name) {
+ assert(!name.empty());
+ return name[0] == '$' ? name.substr(1) : name;
+ }
+
+ template <ExprType T>
+ Value Get(const VarExpr<T>& ve) {
+ return Value{{std::string(VarName(ve.var.name()))}, Precedence::Atomic};
+ }
+
+ template <ExprType T>
+ Value Set(Value& child, const VarExpr<T>& ve) {
+ return WrapChild(child, VarName(ve.var.name()) + " = ", "",
+ Precedence::Assign);
+ }
+
+ std::string TempVarName(Index n) {
+ // FIXME: this needs much better variable naming. Problem is, the code
+ // in generate-names.cc has allready run, its dictionaries deleted, so it
+ // is not easy to integrate with it.
+ return "t" + std::to_string(n);
+ }
+
+ std::string LocalDecl(const std::string& name, Type t) {
+ auto struc = lst.GenTypeDecl(name);
+ return cat(VarName(name), ":",
+ struc.empty() ? GetDecompTypeName(t) : struc);
+ }
+
+ bool ConstIntVal(const Expr* e, uint64_t& dest) {
+ dest = 0;
+ if (!e || e->type() != ExprType::Const) {
+ return false;
+ }
+ auto& c = cast<ConstExpr>(e)->const_;
+ if (c.type() != Type::I32 && c.type() != Type::I64) {
+ return false;
+ }
+ dest = c.type() == Type::I32 ? c.u32() : c.u64();
+ return true;
+ }
+
+ void LoadStore(Value& val,
+ const Node& addr_exp,
+ uint64_t offset,
+ Opcode opc,
+ Address align,
+ Type op_type) {
+ bool append_type = true;
+ auto access = lst.GenAccess(offset, addr_exp);
+ if (!access.empty()) {
+ if (access == "*") {
+ // The variable was declared as a typed pointer, so this access
+ // doesn't need a type.
+ append_type = false;
+ } else {
+ // We can do this load/store as a struct access.
+ BracketIfNeeded(val, Precedence::Indexing);
+ val.v.back() += "." + access;
+ return;
+ }
+ }
+ // Detect absolute addressing, which we try to turn into references to the
+ // data section when possible.
+ uint64_t abs_base;
+ if (ConstIntVal(addr_exp.e, abs_base)) {
+ // We don't care what part of the absolute address was stored where,
+ // 1[0] and 0[1] are the same.
+ abs_base += offset;
+ // FIXME: make this less expensive with a binary search or whatever.
+ for (auto dat : mc.module.data_segments) {
+ uint64_t dat_base;
+ if (dat->offset.size() == 1 &&
+ ConstIntVal(&dat->offset.front(), dat_base) &&
+ abs_base >= dat_base && abs_base < dat_base + dat->data.size()) {
+ // We are inside the range of this data segment!
+ // Turn expression into data_name[index]
+ val = Value{{dat->name}, Precedence::Atomic};
+ // The new offset is from the start of the data segment, instead of
+ // whatever it was.. this may be a different value from both the
+ // original const and offset!
+ offset = abs_base - dat_base;
+ }
+ }
+ }
+ // Do the load/store as a generalized indexing operation.
+ // The offset is divisible by the alignment in 99.99% of
+ // cases, but the spec doesn't guarantee it, so we must
+ // have a backup syntax.
+ auto index = offset % align == 0
+ ? std::to_string(offset / align)
+ : cat(std::to_string(offset), "@", std::to_string(align));
+ // Detect the very common case of (base + (index << 2))[0]:int etc.
+ // so we can instead do base[index]:int
+ // TODO: (index << 2) on the left of + occurs also.
+ // TODO: sadly this does not address cases where the shift amount > align.
+ // (which happens for arrays of structs or arrays of long (with align=4)).
+ // TODO: also very common is (v = base + (index << 2))[0]:int
+ if (addr_exp.etype == ExprType::Binary) {
+ auto& pe = *cast<BinaryExpr>(addr_exp.e);
+ auto& shift_exp = addr_exp.children[1];
+ if (pe.opcode == Opcode::I32Add && shift_exp.etype == ExprType::Binary) {
+ auto& se = *cast<BinaryExpr>(shift_exp.e);
+ auto& const_exp = shift_exp.children[1];
+ if (se.opcode == Opcode::I32Shl && const_exp.etype == ExprType::Const) {
+ auto& ce = *cast<ConstExpr>(const_exp.e);
+ if (ce.const_.type() == Type::I32 &&
+ (1ULL << ce.const_.u32()) == align) {
+ // Pfew, case detected :( Lets re-write this in Haskell.
+ // TODO: we're decompiling these twice.
+ // The thing to the left of << is going to be part of the index.
+ auto ival = DecompileExpr(shift_exp.children[0], &shift_exp);
+ if (ival.v.size() == 1) { // Don't bother if huge.
+ if (offset == 0) {
+ index = ival.v[0];
+ } else {
+ BracketIfNeeded(ival, Precedence::Add);
+ index = cat(ival.v[0], " + ", index);
+ }
+ // We're going to use the thing to the left of + as the new
+ // base address:
+ val = DecompileExpr(addr_exp.children[0], &addr_exp);
+ }
+ }
+ }
+ }
+ }
+ BracketIfNeeded(val, Precedence::Indexing);
+ val.v.back() += cat("[", index, "]");
+ if (append_type) {
+ val.v.back() += cat(":", GetDecompTypeName(GetMemoryType(op_type, opc)),
+ lst.GenAlign(align, opc));
+ }
+ val.precedence = Precedence::Indexing;
+ }
+
+ Value DecompileExpr(const Node& n, const Node* parent) {
+ std::vector<Value> args;
+ for (auto& c : n.children) {
+ args.push_back(DecompileExpr(c, &n));
+ }
+ // First deal with the specialized node types.
+ switch (n.ntype) {
+ case NodeType::FlushToVars: {
+ std::string decls = "let ";
+ for (Index i = 0; i < n.u.var_count; i++) {
+ if (i) {
+ decls += ", ";
+ }
+ decls += TempVarName(n.u.var_start + i);
+ }
+ decls += " = ";
+ return WrapNAry(args, decls, "", Precedence::Assign);
+ }
+ case NodeType::FlushedVar: {
+ return Value{{TempVarName(n.u.var_start)}, Precedence::Atomic};
+ }
+ case NodeType::Statements: {
+ Value stats{{}, Precedence::None};
+ for (size_t i = 0; i < n.children.size(); i++) {
+ auto& s = args[i].v.back();
+ if (s.back() != '}' && s.back() != ':') {
+ s += ';';
+ }
+ std::move(args[i].v.begin(), args[i].v.end(),
+ std::back_inserter(stats.v));
+ }
+ return stats;
+ }
+ case NodeType::EndReturn: {
+ return WrapNAry(args, "return ", "", Precedence::None);
+ }
+ case NodeType::Decl: {
+ cur_ast->vars_defined[n.u.var->name()].defined = true;
+ return Value{{"var " + LocalDecl(std::string(n.u.var->name()),
+ cur_func->GetLocalType(*n.u.var))},
+ Precedence::None};
+ }
+ case NodeType::DeclInit: {
+ if (cur_ast->vars_defined[n.u.var->name()].defined) {
+ // This has already been pre-declared, output as assign.
+ return WrapChild(args[0], cat(VarName(n.u.var->name()), " = "), "",
+ Precedence::None);
+ } else {
+ return WrapChild(args[0],
+ cat("var ",
+ LocalDecl(std::string(n.u.var->name()),
+ cur_func->GetLocalType(*n.u.var)),
+ " = "),
+ "", Precedence::None);
+ }
+ }
+ case NodeType::Expr:
+ // We're going to fall thru to the second switch to deal with ExprType.
+ break;
+ case NodeType::Uninitialized:
+ assert(false);
+ break;
+ }
+ // Existing ExprTypes.
+ switch (n.etype) {
+ case ExprType::Const: {
+ auto& c = cast<ConstExpr>(n.e)->const_;
+ switch (c.type()) {
+ case Type::I32:
+ return Value{{std::to_string(static_cast<int32_t>(c.u32()))},
+ Precedence::Atomic};
+ case Type::I64:
+ return Value{{std::to_string(static_cast<int64_t>(c.u64())) + "L"},
+ Precedence::Atomic};
+ case Type::F32: {
+ float f = Bitcast<float>(c.f32_bits());
+ return Value{{to_string(f) + "f"}, Precedence::Atomic};
+ }
+ case Type::F64: {
+ double d = Bitcast<double>(c.f64_bits());
+ return Value{{to_string(d)}, Precedence::Atomic};
+ }
+ case Type::V128:
+ return Value{{"V128"}, Precedence::Atomic}; // FIXME
+ default:
+ WABT_UNREACHABLE;
+ }
+ }
+ case ExprType::LocalGet: {
+ return Get(*cast<LocalGetExpr>(n.e));
+ }
+ case ExprType::GlobalGet: {
+ return Get(*cast<GlobalGetExpr>(n.e));
+ }
+ case ExprType::LocalSet: {
+ return Set(args[0], *cast<LocalSetExpr>(n.e));
+ }
+ case ExprType::GlobalSet: {
+ return Set(args[0], *cast<GlobalSetExpr>(n.e));
+ }
+ case ExprType::LocalTee: {
+ auto& te = *cast<LocalTeeExpr>(n.e);
+ return args.empty() ? Get(te) : Set(args[0], te);
+ }
+ case ExprType::Binary: {
+ auto& be = *cast<BinaryExpr>(n.e);
+ auto opcs = OpcodeToToken(be.opcode);
+ // TODO: Is this selection better done on Opcode values directly?
+ // What if new values get added and OtherBin doesn't make sense?
+ auto prec = Precedence::OtherBin;
+ if (opcs == "*" || opcs == "/" || opcs == "%") {
+ prec = Precedence::Multiply;
+ } else if (opcs == "+" || opcs == "-") {
+ prec = Precedence::Add;
+ } else if (opcs == "&" || opcs == "|" || opcs == "^") {
+ prec = Precedence::Bit;
+ } else if (opcs == "<<" || opcs == ">>") {
+ prec = Precedence::Shift;
+ }
+ return WrapBinary(args, opcs, false, prec);
+ }
+ case ExprType::Compare: {
+ auto& ce = *cast<CompareExpr>(n.e);
+ return WrapBinary(args, OpcodeToToken(ce.opcode), false,
+ Precedence::Equal);
+ }
+ case ExprType::Unary: {
+ auto& ue = *cast<UnaryExpr>(n.e);
+ // BracketIfNeeded(stack.back());
+ // TODO: also version without () depending on precedence.
+ return WrapChild(args[0], OpcodeToToken(ue.opcode) + "(", ")",
+ Precedence::Atomic);
+ }
+ case ExprType::Load: {
+ auto& le = *cast<LoadExpr>(n.e);
+ LoadStore(args[0], n.children[0], le.offset, le.opcode, le.align,
+ le.opcode.GetResultType());
+ return std::move(args[0]);
+ }
+ case ExprType::Store: {
+ auto& se = *cast<StoreExpr>(n.e);
+ LoadStore(args[0], n.children[0], se.offset, se.opcode, se.align,
+ se.opcode.GetParamType2());
+ return WrapBinary(args, "=", true, Precedence::Assign);
+ }
+ case ExprType::If: {
+ auto ife = cast<IfExpr>(n.e);
+ Value* elsep = nullptr;
+ if (!ife->false_.empty()) {
+ elsep = &args[2];
+ }
+ auto& thenp = args[1];
+ auto& ifs = args[0];
+ bool multiline = ifs.v.size() > 1 || thenp.v.size() > 1;
+ size_t width = ifs.width() + thenp.width();
+ if (elsep) {
+ width += elsep->width();
+ multiline = multiline || elsep->v.size() > 1;
+ }
+ multiline = multiline || width > target_exp_width;
+ if (multiline) {
+ auto if_start = std::string_view("if (");
+ IndentValue(ifs, if_start.size(), if_start);
+ ifs.v.back() += ") {";
+ IndentValue(thenp, indent_amount, {});
+ std::move(thenp.v.begin(), thenp.v.end(), std::back_inserter(ifs.v));
+ if (elsep) {
+ ifs.v.push_back("} else {");
+ IndentValue(*elsep, indent_amount, {});
+ std::move(elsep->v.begin(), elsep->v.end(),
+ std::back_inserter(ifs.v));
+ }
+ ifs.v.push_back("}");
+ ifs.precedence = Precedence::If;
+ return std::move(ifs);
+ } else {
+ auto s = cat("if (", ifs.v[0], ") { ", thenp.v[0], " }");
+ if (elsep)
+ s += cat(" else { ", elsep->v[0], " }");
+ return Value{{std::move(s)}, Precedence::If};
+ }
+ }
+ case ExprType::Block: {
+ auto& val = args[0];
+ val.v.push_back(
+ cat("label ", VarName(cast<BlockExpr>(n.e)->block.label), ":"));
+ // If this block is part of a larger statement scope, it doesn't
+ // need its own indenting, but if its part of an exp we wrap it in {}.
+ if (parent && parent->ntype != NodeType::Statements &&
+ parent->etype != ExprType::Block &&
+ parent->etype != ExprType::Loop &&
+ (parent->etype != ExprType::If || &parent->children[0] == &n)) {
+ IndentValue(val, indent_amount, {});
+ val.v.insert(val.v.begin(), "{");
+ val.v.push_back("}");
+ }
+ val.precedence = Precedence::Atomic;
+ return std::move(val);
+ }
+ case ExprType::Loop: {
+ auto& val = args[0];
+ auto& block = cast<LoopExpr>(n.e)->block;
+ IndentValue(val, indent_amount, {});
+ val.v.insert(val.v.begin(), cat("loop ", VarName(block.label), " {"));
+ val.v.push_back("}");
+ val.precedence = Precedence::Atomic;
+ return std::move(val);
+ }
+ case ExprType::Br: {
+ auto be = cast<BrExpr>(n.e);
+ return Value{{(n.u.lt == LabelType::Loop ? "continue " : "goto ") +
+ VarName(be->var.name())},
+ Precedence::None};
+ }
+ case ExprType::BrIf: {
+ auto bie = cast<BrIfExpr>(n.e);
+ auto jmp = n.u.lt == LabelType::Loop ? "continue" : "goto";
+ return WrapChild(args[0], "if (",
+ cat(") ", jmp, " ", VarName(bie->var.name())),
+ Precedence::None);
+ }
+ case ExprType::Return: {
+ return WrapNAry(args, "return ", "", Precedence::None);
+ }
+ case ExprType::Rethrow: {
+ return WrapNAry(args, "rethrow ", "", Precedence::None);
+ }
+ case ExprType::Drop: {
+ // Silent dropping of return values is very common, so currently
+ // don't output this.
+ return std::move(args[0]);
+ }
+ case ExprType::Nop: {
+ return Value{{"nop"}, Precedence::None};
+ }
+ case ExprType::Unreachable: {
+ return Value{{"unreachable"}, Precedence::None};
+ }
+ case ExprType::RefNull: {
+ return Value{{"null"}, Precedence::Atomic};
+ }
+ case ExprType::BrTable: {
+ auto bte = cast<BrTableExpr>(n.e);
+ std::string ts = "br_table[";
+ for (auto& v : bte->targets) {
+ ts += VarName(v.name());
+ ts += ", ";
+ }
+ ts += "..";
+ ts += VarName(bte->default_target.name());
+ ts += "](";
+ return WrapChild(args[0], ts, ")", Precedence::Atomic);
+ }
+ case ExprType::CodeMetadata: {
+ auto cme = cast<CodeMetadataExpr>(n.e);
+ std::string c = "// @metadata.code." + cme->name + " ";
+ c += BinaryToString(cme->data);
+ return Value{{std::move(c)}, Precedence::None};
+ }
+ default: {
+ // Everything that looks like a function call.
+ std::string name;
+ auto precedence = Precedence::Atomic;
+ switch (n.etype) {
+ case ExprType::Call:
+ name = cast<CallExpr>(n.e)->var.name();
+ break;
+ case ExprType::ReturnCall:
+ name = "return_call " + cast<ReturnCallExpr>(n.e)->var.name();
+ precedence = Precedence::None;
+ break;
+ case ExprType::Convert:
+ name = std::string(OpcodeToToken(cast<ConvertExpr>(n.e)->opcode));
+ break;
+ case ExprType::Ternary:
+ name = std::string(OpcodeToToken(cast<TernaryExpr>(n.e)->opcode));
+ break;
+ case ExprType::Select:
+ // This one looks like it could be translated to "?:" style ternary,
+ // but the arguments are NOT lazy, and side effects definitely do
+ // occur in the branches. So it has no clear equivalent in C-syntax.
+ // To emphasize that all args are being evaluated in order, we
+ // leave it as a function call.
+ name = "select_if";
+ break;
+ case ExprType::MemoryGrow:
+ name = "memory_grow";
+ break;
+ case ExprType::MemorySize:
+ name = "memory_size";
+ break;
+ case ExprType::MemoryCopy:
+ name = "memory_copy";
+ break;
+ case ExprType::MemoryFill:
+ name = "memory_fill";
+ break;
+ case ExprType::RefIsNull:
+ name = "is_null";
+ break;
+ case ExprType::CallIndirect:
+ name = "call_indirect";
+ break;
+ case ExprType::ReturnCallIndirect:
+ name = "return_call call_indirect";
+ break;
+ default:
+ name = GetExprTypeName(n.etype);
+ break;
+ }
+ return WrapNAry(args, name + "(", ")", precedence);
+ }
+ }
+ }
+
+ bool CheckImportExport(std::string& s,
+ ExternalKind kind,
+ Index index,
+ std::string_view name) {
+ // Figure out if this thing is imported, exported, or neither.
+ auto is_import = mc.module.IsImport(kind, Var(index, Location()));
+ // TODO: is this the best way to check for export?
+ // FIXME: this doesn't work for functions that get renamed in some way,
+ // as the export has the original name..
+ auto xport = mc.module.GetExport(name);
+ auto is_export = xport && xport->kind == kind;
+ if (is_export)
+ s += "export ";
+ if (is_import)
+ s += "import ";
+ return is_import;
+ }
+
+ std::string InitExp(const ExprList& el) {
+ assert(!el.empty());
+ AST ast(mc, nullptr);
+ ast.Construct(el, 1, 0, false);
+ auto val = DecompileExpr(ast.exp_stack[0], nullptr);
+ assert(ast.exp_stack.size() == 1 && val.v.size() == 1);
+ return std::move(val.v[0]);
+ }
+
+ // FIXME: Merge with WatWriter::WriteQuotedData somehow.
+ std::string BinaryToString(const std::vector<uint8_t>& in) {
+ std::string s = "\"";
+ size_t line_start = 0;
+ static const char s_hexdigits[] = "0123456789abcdef";
+ for (auto c : in) {
+ if (c >= ' ' && c <= '~') {
+ s += c;
+ } else {
+ s += '\\';
+ s += s_hexdigits[c >> 4];
+ s += s_hexdigits[c & 0xf];
+ }
+ if (s.size() - line_start > target_exp_width) {
+ if (line_start == 0) {
+ s = " " + s;
+ }
+ s += "\"\n ";
+ line_start = s.size();
+ s += "\"";
+ }
+ }
+ s += '\"';
+ return s;
+ }
+
+ std::string Decompile() {
+ std::string s;
+ // Memories.
+ Index memory_index = 0;
+ for (auto m : mc.module.memories) {
+ auto is_import =
+ CheckImportExport(s, ExternalKind::Memory, memory_index, m->name);
+ s += cat("memory ", m->name);
+ if (!is_import) {
+ s += cat("(initial: ", std::to_string(m->page_limits.initial),
+ ", max: ", std::to_string(m->page_limits.max), ")");
+ }
+ s += ";\n";
+ memory_index++;
+ }
+ if (!mc.module.memories.empty())
+ s += "\n";
+
+ // Globals.
+ Index global_index = 0;
+ for (auto g : mc.module.globals) {
+ auto is_import =
+ CheckImportExport(s, ExternalKind::Global, global_index, g->name);
+ s += cat("global ", g->name, ":", GetDecompTypeName(g->type));
+ if (!is_import) {
+ s += cat(" = ", InitExp(g->init_expr));
+ }
+ s += ";\n";
+ global_index++;
+ }
+ if (!mc.module.globals.empty())
+ s += "\n";
+
+ // Tables.
+ Index table_index = 0;
+ for (auto tab : mc.module.tables) {
+ auto is_import =
+ CheckImportExport(s, ExternalKind::Table, table_index, tab->name);
+ s += cat("table ", tab->name, ":", GetDecompTypeName(tab->elem_type));
+ if (!is_import) {
+ s += cat("(min: ", std::to_string(tab->elem_limits.initial),
+ ", max: ", std::to_string(tab->elem_limits.max), ")");
+ }
+ s += ";\n";
+ table_index++;
+ }
+ if (!mc.module.tables.empty())
+ s += "\n";
+
+ // Data.
+ for (auto dat : mc.module.data_segments) {
+ s += cat("data ", dat->name, "(offset: ", InitExp(dat->offset), ") =");
+ auto ds = BinaryToString(dat->data);
+ if (ds.size() > target_exp_width / 2) {
+ s += "\n";
+ } else {
+ s += " ";
+ }
+ s += ds;
+ s += ";\n";
+ }
+ if (!mc.module.data_segments.empty())
+ s += "\n";
+
+ // Code.
+ Index func_index = 0;
+ for (auto f : mc.module.funcs) {
+ cur_func = f;
+ auto is_import =
+ CheckImportExport(s, ExternalKind::Func, func_index, f->name);
+ AST ast(mc, f);
+ cur_ast = &ast;
+ if (!is_import) {
+ ast.Construct(f->exprs, f->GetNumResults(), 0, true);
+ lst.Track(ast.exp_stack[0]);
+ lst.CheckLayouts();
+ }
+ s += cat("function ", f->name, "(");
+ for (Index i = 0; i < f->GetNumParams(); i++) {
+ if (i)
+ s += ", ";
+ auto t = f->GetParamType(i);
+ auto name = "$" + IndexToAlphaName(i);
+ s += LocalDecl(name, t);
+ }
+ s += ")";
+ if (f->GetNumResults()) {
+ if (f->GetNumResults() == 1) {
+ s += cat(":", GetDecompTypeName(f->GetResultType(0)));
+ } else {
+ s += ":(";
+ for (Index i = 0; i < f->GetNumResults(); i++) {
+ if (i)
+ s += ", ";
+ s += GetDecompTypeName(f->GetResultType(i));
+ }
+ s += ")";
+ }
+ }
+ if (is_import) {
+ s += ";";
+ } else {
+ s += " {\n";
+ auto val = DecompileExpr(ast.exp_stack[0], nullptr);
+ IndentValue(val, indent_amount, {});
+ for (auto& stat : val.v) {
+ s += stat;
+ s += "\n";
+ }
+ s += "}";
+ }
+ s += "\n\n";
+ mc.EndFunc();
+ lst.Clear();
+ func_index++;
+ cur_ast = nullptr;
+ cur_func = nullptr;
+ }
+ return s;
+ }
+
+ ModuleContext mc;
+ const DecompileOptions& options;
+ size_t indent_amount = 2;
+ size_t target_exp_width = 70;
+ const Func* cur_func = nullptr;
+ AST* cur_ast = nullptr;
+ LoadStoreTracking lst;
+};
+
+std::string Decompile(const Module& module, const DecompileOptions& options) {
+ Decompiler decompiler(module, options);
+ return decompiler.Decompile();
+}
+
+} // namespace wabt