1 /*
2 * Copyright 2019 WebAssembly Community Group participants
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef WABT_DECOMPILER_LS_H_
18 #define WABT_DECOMPILER_LS_H_
19
20 #include "src/decompiler-ast.h"
21
22 #include <map>
23
24 namespace wabt {
25
26 // Names starting with "u" are unsigned, the rest are "signed or doesn't matter"
GetDecompTypeName(Type t)27 inline const char *GetDecompTypeName(Type t) {
28 switch (t) {
29 case Type::I8: return "byte";
30 case Type::I8U: return "ubyte";
31 case Type::I16: return "short";
32 case Type::I16U: return "ushort";
33 case Type::I32: return "int";
34 case Type::I32U: return "uint";
35 case Type::I64: return "long";
36 case Type::F32: return "float";
37 case Type::F64: return "double";
38 case Type::V128: return "simd";
39 case Type::Func: return "func";
40 case Type::FuncRef: return "funcref";
41 case Type::ExternRef: return "externref";
42 case Type::Void: return "void";
43 default: return "ILLEGAL";
44 }
45 }
46
GetMemoryType(Type operand_type,Opcode opc)47 inline Type GetMemoryType(Type operand_type, Opcode opc) {
48 // TODO: something something SIMD.
49 // TODO: this loses information of the type it is read into.
50 // That may well not be the biggest deal since that is usually obvious
51 // from context, if not, we should probably represent that as a cast around
52 // the access, since it should not be part of the field type.
53 if (operand_type == Type::I32 || operand_type == Type::I64) {
54 auto name = string_view(opc.GetName());
55 // FIXME: change into a new column in opcode.def instead?
56 auto is_unsigned = name.substr(name.size() - 2) == "_u";
57 switch (opc.GetMemorySize()) {
58 case 1: return is_unsigned ? Type::I8U : Type::I8;
59 case 2: return is_unsigned ? Type::I16U : Type::I16;
60 case 4: return is_unsigned ? Type::I32U : Type::I32;
61 }
62 }
63 return operand_type;
64 }
65
66 // Track all loads and stores inside a single function, to be able to detect
67 // struct layouts we can use to annotate variables with, to make code more
68 // readable.
69 struct LoadStoreTracking {
70 struct LSAccess {
71 Address byte_size = 0;
72 Type type = Type::Any;
73 Address align = 0;
74 uint32_t idx = 0;
75 bool is_uniform = true;
76 };
77
78 struct LSVar {
79 std::map<uint64_t, LSAccess> accesses;
80 bool struct_layout = true;
81 Type same_type = Type::Any;
82 Address same_align = kInvalidAddress;
83 Opcode last_opc;
84 };
85
TrackLoadStoreTracking86 void Track(const Node& n) {
87 for (auto& c : n.children) Track(c);
88 switch (n.etype) {
89 case ExprType::Load: {
90 auto& le = *cast<LoadExpr>(n.e);
91 LoadStore(le.offset, le.opcode, le.opcode.GetResultType(),
92 le.align, n.children[0]);
93 break;
94 }
95 case ExprType::Store: {
96 auto& se = *cast<StoreExpr>(n.e);
97 LoadStore(se.offset, se.opcode, se.opcode.GetParamType2(),
98 se.align, n.children[0]);
99 break;
100 }
101 default:
102 break;
103 }
104 }
105
AddrExpNameLoadStoreTracking106 const std::string AddrExpName(const Node& addr_exp) const {
107 // TODO: expand this to more kinds of address expressions.
108 switch (addr_exp.etype) {
109 case ExprType::LocalGet:
110 return cast<LocalGetExpr>(addr_exp.e)->var.name();
111 break;
112 case ExprType::LocalTee:
113 return cast<LocalTeeExpr>(addr_exp.e)->var.name();
114 break;
115 default:
116 return "";
117 }
118 }
119
LoadStoreLoadStoreTracking120 void LoadStore(uint64_t offset, Opcode opc, Type type, Address align,
121 const Node& addr_exp) {
122 auto byte_size = opc.GetMemorySize();
123 type = GetMemoryType(type, opc);
124 // We want to associate memory ops of a certain offset & size as being
125 // relative to a uniquely identifiable pointer, such as a local.
126 auto name = AddrExpName(addr_exp);
127 if (name.empty()) {
128 return;
129 }
130 auto& var = vars[name];
131 auto& access = var.accesses[offset];
132 // Check if previous access at this offset (if any) is of same size
133 // and type (see Checklayouts below).
134 if (access.byte_size &&
135 ((access.byte_size != byte_size) ||
136 (access.type != type) ||
137 (access.align != align)))
138 access.is_uniform = false;
139 // Also exclude weird alignment accesses from structs.
140 if (!opc.IsNaturallyAligned(align))
141 access.is_uniform = false;
142 access.byte_size = byte_size;
143 access.type = type;
144 access.align = align;
145 // Additionally, check if all accesses are to the same type, so
146 // if layout check fails, we can at least declare it as pointer to
147 // a type.
148 if ((var.same_type == type || var.same_type == Type::Any) &&
149 (var.same_align == align || var.same_align == kInvalidAddress)) {
150 var.same_type = type;
151 var.same_align = align;
152 var.last_opc = opc;
153 } else {
154 var.same_type = Type::Void;
155 var.same_align = kInvalidAddress;
156 }
157 }
158
CheckLayoutsLoadStoreTracking159 void CheckLayouts() {
160 // Here we check if the set of accesses we have collected form a sequence
161 // we could declare as a struct, meaning they are properly aligned,
162 // contiguous, and have no overlaps between different types and sizes.
163 // We do this because an int access of size 2 at offset 0 followed by
164 // a float access of size 4 at offset 4 can compactly represented as a
165 // struct { short, float }, whereas something that reads from overlapping
166 // or discontinuous offsets would need a more complicated syntax that
167 // involves explicit offsets.
168 // We assume that the bulk of memory accesses are of this very regular kind,
169 // so we choose not to even emit struct layouts for irregular ones,
170 // given that they are rare and confusing, and thus do not benefit from
171 // being represented as if they were structs.
172 for (auto& var : vars) {
173 if (var.second.accesses.size() == 1) {
174 // If we have just one access, this is better represented as a pointer
175 // than a struct.
176 var.second.struct_layout = false;
177 continue;
178 }
179 uint64_t cur_offset = 0;
180 uint32_t idx = 0;
181 for (auto& access : var.second.accesses) {
182 access.second.idx = idx++;
183 if (!access.second.is_uniform) {
184 var.second.struct_layout = false;
185 break;
186 }
187 // Align to next access: all elements are expected to be aligned to
188 // a memory address thats a multiple of their own size.
189 auto mask = static_cast<uint64_t>(access.second.byte_size - 1);
190 cur_offset = (cur_offset + mask) & ~mask;
191 if (cur_offset != access.first) {
192 var.second.struct_layout = false;
193 break;
194 }
195 cur_offset += access.second.byte_size;
196 }
197 }
198 }
199
IdxToNameLoadStoreTracking200 std::string IdxToName(uint32_t idx) const {
201 return IndexToAlphaName(idx); // TODO: more descriptive names?
202 }
203
GenAlignLoadStoreTracking204 std::string GenAlign(Address align, Opcode opc) const {
205 return opc.IsNaturallyAligned(align)
206 ? ""
207 : cat("@", std::to_string(align));
208 }
209
GenTypeDeclLoadStoreTracking210 std::string GenTypeDecl(const std::string& name) const {
211 auto it = vars.find(name);
212 if (it == vars.end()) {
213 return "";
214 }
215 if (it->second.struct_layout) {
216 std::string s = "{ ";
217 for (auto& access : it->second.accesses) {
218 if (access.second.idx) s += ", ";
219 s += IdxToName(access.second.idx);
220 s += ':';
221 s += GetDecompTypeName(access.second.type);
222 }
223 s += " }";
224 return s;
225 }
226 // We don't have a struct layout, or the struct has just one field,
227 // so maybe we can just declare it as a pointer to one type?
228 if (it->second.same_type != Type::Void) {
229 return cat(GetDecompTypeName(it->second.same_type), "_ptr",
230 GenAlign(it->second.same_align, it->second.last_opc));
231 }
232 return "";
233 }
234
GenAccessLoadStoreTracking235 std::string GenAccess(uint64_t offset, const Node& addr_exp) const {
236 auto name = AddrExpName(addr_exp);
237 if (name.empty()) {
238 return "";
239 }
240 auto it = vars.find(name);
241 if (it == vars.end()) {
242 return "";
243 }
244 if (it->second.struct_layout) {
245 auto ait = it->second.accesses.find(offset);
246 assert (ait != it->second.accesses.end());
247 return IdxToName(ait->second.idx);
248 }
249 // Not a struct, see if it is a typed pointer.
250 if (it->second.same_type != Type::Void) {
251 return "*";
252 }
253 return "";
254 }
255
ClearLoadStoreTracking256 void Clear() {
257 vars.clear();
258 }
259
260 std::map<std::string, LSVar> vars;
261 };
262
263 } // namespace wabt
264
265 #endif // WABT_DECOMPILER_LS_H_
266