diff --git a/src/decompiler.cc b/src/decompiler.cc index bd6a5af732..fc6f4898f3 100644 --- a/src/decompiler.cc +++ b/src/decompiler.cc @@ -226,16 +226,62 @@ struct Decompiler { void LoadStore(Value &val, const Node& addr_exp, uint32_t offset, Opcode opc, Address align, Type op_type) { - BracketIfNeeded(val, Precedence::Indexing); auto access = lst.GenAccess(offset, addr_exp); + if (!access.empty()) { + // We can do this load/store as a struct access. + BracketIfNeeded(val, Precedence::Indexing); + val.v.back() += "." + access; + return; + } + // 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(addr_exp.e); + auto& shift_exp = addr_exp.children[1]; + if (pe.opcode == Opcode::I32Add && + shift_exp.etype == ExprType::Binary) { + auto& se = *cast(shift_exp.e); + auto& const_exp = shift_exp.children[1]; + if (se.opcode == Opcode::I32Shl && + const_exp.etype == ExprType::Const) { + auto& ce = *cast(const_exp.e); + if (ce.const_.type == Type::I32 && (1 << 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() += - !access.empty() - ? "." + access - : cat("[", std::to_string(offset), - "]:", GetDecompTypeName(GetMemoryType(op_type, opc)), - !opc.IsNaturallyAligned(align) - ? cat("@" + std::to_string(align)) - : ""); + cat("[", index, "]:", GetDecompTypeName(GetMemoryType(op_type, opc)), + opc.IsNaturallyAligned(align) + ? "" + : cat("@", std::to_string(align))); } Value DecompileExpr(const Node& n, const Node* parent) { diff --git a/test/decompile/basic.txt b/test/decompile/basic.txt index 24d2a38520..34c6f57810 100644 --- a/test/decompile/basic.txt +++ b/test/decompile/basic.txt @@ -28,7 +28,7 @@ if i32.const 1 i32.const 2 - i32.load offset=3 + i32.load offset=3 align=1 i32.const 5 i32.add i32.store offset=4 @@ -126,7 +126,7 @@ export function f(a:int, b:int):int { var c:long = 8L; var d:float = 6.0f; var e:double = 7.0; - if (e < 10.0) { 1[4]:int = 2[3]:int + 5 } + if (e < 10.0) { 1[1]:int = 2[3]:int@1 + 5 } f(a + g_b, 9); loop L_b { if (if (0) { 1 } else { 2 }) goto B_c; diff --git a/test/decompile/loadstore.txt b/test/decompile/loadstore.txt index d1216950f2..a05d0e29c0 100644 --- a/test/decompile/loadstore.txt +++ b/test/decompile/loadstore.txt @@ -53,8 +53,48 @@ drop ;; 4) Unaligned access / access with unexpected gaps. get_local 7 - f32.load offset=1 + f32.load offset=1 align=1 drop + ;; Test index rewriting. + ;; code that does (base + (index << 2))[0]:int is super common. + get_local 0 + get_local 1 + i32.const 2 + i32.shl + i32.add + get_local 0 + get_local 1 + i32.const 2 + i32.shl + i32.add + i32.load offset=0 + i32.store offset=0 + ;; Same with non-zero offsets. + get_local 0 + get_local 1 + i32.const 2 + i32.shl + i32.add + get_local 0 + get_local 1 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + i32.store offset=4 + ;; If the shift amount does not match, it doesn't work. + get_local 0 + get_local 1 + i32.const 3 + i32.shl + i32.add + get_local 0 + get_local 1 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + i32.store offset=4 ) (export "f" (func $f)) ) @@ -77,7 +117,10 @@ export function f(a:{ a:float, b:float }, b:{ a:ushort, b:long }) { f[0]:short; g[0]:int; g[0]:int@1; - h[1]:float; + h[1]:float@1; + a[b]:int = a[b]:int; + a[b + 1]:int = a[b + 1]:int; + (a + (b << 3))[1]:int = a[b + 1]:int; } ;;; STDOUT ;;)