diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/wasm2c/src/decompiler.cc | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
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.cc | 832 |
1 files changed, 832 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..25fd75a1bb --- /dev/null +++ b/third_party/wasm2c/src/decompiler.cc @@ -0,0 +1,832 @@ +/* + * 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 "src/decompiler.h" + +#include "src/decompiler-ast.h" +#include "src/decompiler-ls.h" +#include "src/decompiler-naming.h" + +#include "src/stream.h" + +#define WABT_TRACING 0 +#include "src/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, string_view first_indent) { + auto indent = Indent(amount); + for (auto& stat : val.v) { + auto is = (&stat != &val.v[0] || first_indent.empty()) + ? string_view(indent) + : first_indent; + stat.insert(0, is.data(), is.size()); + } + } + + Value WrapChild(Value &child, string_view prefix, 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, 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, string_view prefix, + 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 : 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; + } + } + + string_view VarName(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 = 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); + } + 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, + string_view name) { + // Figure out if this thing is imported, exported, or neither. + auto is_import = mc.module.IsImport(kind, Var(index)); + // 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 = * + 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 |