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