Source code

Revision control

Copy as Markdown

Other Tools

/*
* 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
*
*
* 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