1 | /* |
2 | * Copyright 2017 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 | // |
18 | // Instruments the build with code to intercept all memory reads and writes. |
19 | // This can be useful in building tools that analyze memory access behaviour. |
20 | // |
21 | // Before: |
22 | // (i32.load8_s align=1 offset=2 (i32.const 3)) |
23 | // |
24 | // After: |
25 | // (call $load_val_i32 |
26 | // (i32.const n) // ID |
27 | // (i32.load8_s align=1 offset=2 |
28 | // (call $load_ptr |
29 | // (i32.const n) // ID |
30 | // (i32.const 1) // bytes |
31 | // (i32.const 2) // offset |
32 | // (i32.const 3) // address |
33 | // ) |
34 | // ) |
35 | // ) |
36 | // Stores: store(id, bytes, offset, address) => address |
37 | // |
38 | // Before: |
39 | // (i32.store8 align=1 offset=2 (i32.const 3) (i32.const 4)) |
40 | // |
41 | // After: |
42 | // (i32.store16 align=1 offset=2 |
43 | // (call $store_ptr |
44 | // (i32.const n) // ID |
45 | // (i32.const 1) // bytes |
46 | // (i32.const 2) // offset |
47 | // (i32.const 3) // address |
48 | // ) |
49 | // (call $store_val_i32 |
50 | // (i32.const n) // ID |
51 | // (i32.const 4) |
52 | // ) |
53 | // ) |
54 | // |
55 | // GC struct and array operations are similarly instrumented, but without their |
56 | // pointers (which are references), and we only log MVP wasm types (i.e., not |
57 | // references). |
58 | // |
59 | |
60 | #include "asmjs/shared-constants.h" |
61 | #include "shared-constants.h" |
62 | #include <pass.h> |
63 | #include <wasm-builder.h> |
64 | #include <wasm.h> |
65 | |
66 | namespace wasm { |
67 | |
68 | static Name load_ptr("load_ptr" ); |
69 | static Name load_val_i32("load_val_i32" ); |
70 | static Name load_val_i64("load_val_i64" ); |
71 | static Name load_val_f32("load_val_f32" ); |
72 | static Name load_val_f64("load_val_f64" ); |
73 | static Name store_ptr("store_ptr" ); |
74 | static Name store_val_i32("store_val_i32" ); |
75 | static Name store_val_i64("store_val_i64" ); |
76 | static Name store_val_f32("store_val_f32" ); |
77 | static Name store_val_f64("store_val_f64" ); |
78 | static Name struct_get_val_i32("struct_get_val_i32" ); |
79 | static Name struct_get_val_i64("struct_get_val_i64" ); |
80 | static Name struct_get_val_f32("struct_get_val_f32" ); |
81 | static Name struct_get_val_f64("struct_get_val_f64" ); |
82 | static Name struct_set_val_i32("struct_set_val_i32" ); |
83 | static Name struct_set_val_i64("struct_set_val_i64" ); |
84 | static Name struct_set_val_f32("struct_set_val_f32" ); |
85 | static Name struct_set_val_f64("struct_set_val_f64" ); |
86 | static Name array_get_val_i32("array_get_val_i32" ); |
87 | static Name array_get_val_i64("array_get_val_i64" ); |
88 | static Name array_get_val_f32("array_get_val_f32" ); |
89 | static Name array_get_val_f64("array_get_val_f64" ); |
90 | static Name array_set_val_i32("array_set_val_i32" ); |
91 | static Name array_set_val_i64("array_set_val_i64" ); |
92 | static Name array_set_val_f32("array_set_val_f32" ); |
93 | static Name array_set_val_f64("array_set_val_f64" ); |
94 | static Name array_get_index("array_get_index" ); |
95 | static Name array_set_index("array_set_index" ); |
96 | |
97 | // TODO: Add support for atomicRMW/cmpxchg |
98 | |
99 | struct InstrumentMemory : public WalkerPass<PostWalker<InstrumentMemory>> { |
100 | void visitLoad(Load* curr) { |
101 | id++; |
102 | Builder builder(*getModule()); |
103 | auto mem = getModule()->getMemory(curr->memory); |
104 | auto indexType = mem->indexType; |
105 | auto offset = builder.makeConstPtr(val: curr->offset.addr, indexType: indexType); |
106 | curr->ptr = builder.makeCall(load_ptr, |
107 | {builder.makeConst(x: int32_t(id)), |
108 | builder.makeConst(x: int32_t(curr->bytes)), |
109 | offset, |
110 | curr->ptr}, |
111 | indexType); |
112 | Name target; |
113 | switch (curr->type.getBasic()) { |
114 | case Type::i32: |
115 | target = load_val_i32; |
116 | break; |
117 | case Type::i64: |
118 | target = load_val_i64; |
119 | break; |
120 | case Type::f32: |
121 | target = load_val_f32; |
122 | break; |
123 | case Type::f64: |
124 | target = load_val_f64; |
125 | break; |
126 | default: |
127 | return; // TODO: other types, unreachable, etc. |
128 | } |
129 | replaceCurrent(builder.makeCall( |
130 | target, {builder.makeConst(x: int32_t(id)), curr}, curr->type)); |
131 | } |
132 | |
133 | void visitStore(Store* curr) { |
134 | id++; |
135 | Builder builder(*getModule()); |
136 | auto mem = getModule()->getMemory(curr->memory); |
137 | auto indexType = mem->indexType; |
138 | auto offset = builder.makeConstPtr(val: curr->offset.addr, indexType: indexType); |
139 | curr->ptr = builder.makeCall(store_ptr, |
140 | {builder.makeConst(x: int32_t(id)), |
141 | builder.makeConst(x: int32_t(curr->bytes)), |
142 | offset, |
143 | curr->ptr}, |
144 | indexType); |
145 | Name target; |
146 | switch (curr->value->type.getBasic()) { |
147 | case Type::i32: |
148 | target = store_val_i32; |
149 | break; |
150 | case Type::i64: |
151 | target = store_val_i64; |
152 | break; |
153 | case Type::f32: |
154 | target = store_val_f32; |
155 | break; |
156 | case Type::f64: |
157 | target = store_val_f64; |
158 | break; |
159 | default: |
160 | return; // TODO: other types, unreachable, etc. |
161 | } |
162 | curr->value = builder.makeCall( |
163 | target, {builder.makeConst(x: int32_t(id)), curr->value}, curr->value->type); |
164 | } |
165 | |
166 | void visitStructGet(StructGet* curr) { |
167 | Builder builder(*getModule()); |
168 | Name target; |
169 | if (curr->type == Type::i32) { |
170 | target = struct_get_val_i32; |
171 | } else if (curr->type == Type::i64) { |
172 | target = struct_get_val_i64; |
173 | } else if (curr->type == Type::f32) { |
174 | target = struct_get_val_f32; |
175 | } else if (curr->type == Type::f64) { |
176 | target = struct_get_val_f64; |
177 | } else { |
178 | return; // TODO: other types, unreachable, etc. |
179 | } |
180 | replaceCurrent(builder.makeCall( |
181 | target, {builder.makeConst(x: int32_t(id++)), curr}, curr->type)); |
182 | } |
183 | |
184 | void visitStructSet(StructSet* curr) { |
185 | Builder builder(*getModule()); |
186 | Name target; |
187 | if (curr->value->type == Type::i32) { |
188 | target = struct_set_val_i32; |
189 | } else if (curr->value->type == Type::i64) { |
190 | target = struct_set_val_i64; |
191 | } else if (curr->value->type == Type::f32) { |
192 | target = struct_set_val_f32; |
193 | } else if (curr->value->type == Type::f64) { |
194 | target = struct_set_val_f64; |
195 | } else { |
196 | return; // TODO: other types, unreachable, etc. |
197 | } |
198 | curr->value = |
199 | builder.makeCall(target, |
200 | {builder.makeConst(x: int32_t(id++)), curr->value}, |
201 | curr->value->type); |
202 | } |
203 | |
204 | void visitArrayGet(ArrayGet* curr) { |
205 | Builder builder(*getModule()); |
206 | curr->index = |
207 | builder.makeCall(array_get_index, |
208 | {builder.makeConst(x: int32_t(id++)), curr->index}, |
209 | Type::i32); |
210 | Name target; |
211 | if (curr->type == Type::i32) { |
212 | target = array_get_val_i32; |
213 | } else if (curr->type == Type::i64) { |
214 | target = array_get_val_i64; |
215 | } else if (curr->type == Type::f32) { |
216 | target = array_get_val_f32; |
217 | } else if (curr->type == Type::f64) { |
218 | target = array_get_val_f64; |
219 | } else { |
220 | return; // TODO: other types, unreachable, etc. |
221 | } |
222 | replaceCurrent(builder.makeCall( |
223 | target, {builder.makeConst(x: int32_t(id++)), curr}, curr->type)); |
224 | } |
225 | |
226 | void visitArraySet(ArraySet* curr) { |
227 | Builder builder(*getModule()); |
228 | curr->index = |
229 | builder.makeCall(array_set_index, |
230 | {builder.makeConst(x: int32_t(id++)), curr->index}, |
231 | Type::i32); |
232 | Name target; |
233 | if (curr->value->type == Type::i32) { |
234 | target = array_set_val_i32; |
235 | } else if (curr->value->type == Type::i64) { |
236 | target = array_set_val_i64; |
237 | } else if (curr->value->type == Type::f32) { |
238 | target = array_set_val_f32; |
239 | } else if (curr->value->type == Type::f64) { |
240 | target = array_set_val_f64; |
241 | } else { |
242 | return; // TODO: other types, unreachable, etc. |
243 | } |
244 | curr->value = |
245 | builder.makeCall(target, |
246 | {builder.makeConst(x: int32_t(id++)), curr->value}, |
247 | curr->value->type); |
248 | } |
249 | |
250 | void visitModule(Module* curr) { |
251 | auto indexType = |
252 | curr->memories.empty() ? Type::i32 : curr->memories[0]->indexType; |
253 | |
254 | // Load. |
255 | addImport( |
256 | curr, name: load_ptr, params: {Type::i32, Type::i32, indexType, indexType}, results: indexType); |
257 | addImport(curr, load_val_i32, {Type::i32, Type::i32}, Type::i32); |
258 | addImport(curr, load_val_i64, {Type::i32, Type::i64}, Type::i64); |
259 | addImport(curr, load_val_f32, {Type::i32, Type::f32}, Type::f32); |
260 | addImport(curr, load_val_f64, {Type::i32, Type::f64}, Type::f64); |
261 | |
262 | // Store. |
263 | addImport( |
264 | curr, name: store_ptr, params: {Type::i32, Type::i32, indexType, indexType}, results: indexType); |
265 | addImport(curr, store_val_i32, {Type::i32, Type::i32}, Type::i32); |
266 | addImport(curr, store_val_i64, {Type::i32, Type::i64}, Type::i64); |
267 | addImport(curr, store_val_f32, {Type::i32, Type::f32}, Type::f32); |
268 | addImport(curr, store_val_f64, {Type::i32, Type::f64}, Type::f64); |
269 | |
270 | if (curr->features.hasGC()) { |
271 | // Struct get/set. |
272 | addImport(curr, struct_get_val_i32, {Type::i32, Type::i32}, Type::i32); |
273 | addImport(curr, struct_get_val_i64, {Type::i32, Type::i64}, Type::i64); |
274 | addImport(curr, struct_get_val_f32, {Type::i32, Type::f32}, Type::f32); |
275 | addImport(curr, struct_get_val_f64, {Type::i32, Type::f64}, Type::f64); |
276 | addImport(curr, struct_set_val_i32, {Type::i32, Type::i32}, Type::i32); |
277 | addImport(curr, struct_set_val_i64, {Type::i32, Type::i64}, Type::i64); |
278 | addImport(curr, struct_set_val_f32, {Type::i32, Type::f32}, Type::f32); |
279 | addImport(curr, struct_set_val_f64, {Type::i32, Type::f64}, Type::f64); |
280 | |
281 | // Array get/set. |
282 | addImport(curr, array_get_val_i32, {Type::i32, Type::i32}, Type::i32); |
283 | addImport(curr, array_get_val_i64, {Type::i32, Type::i64}, Type::i64); |
284 | addImport(curr, array_get_val_f32, {Type::i32, Type::f32}, Type::f32); |
285 | addImport(curr, array_get_val_f64, {Type::i32, Type::f64}, Type::f64); |
286 | addImport(curr, array_set_val_i32, {Type::i32, Type::i32}, Type::i32); |
287 | addImport(curr, array_set_val_i64, {Type::i32, Type::i64}, Type::i64); |
288 | addImport(curr, array_set_val_f32, {Type::i32, Type::f32}, Type::f32); |
289 | addImport(curr, array_set_val_f64, {Type::i32, Type::f64}, Type::f64); |
290 | addImport(curr, array_get_index, {Type::i32, Type::i32}, Type::i32); |
291 | addImport(curr, array_set_index, {Type::i32, Type::i32}, Type::i32); |
292 | } |
293 | } |
294 | |
295 | private: |
296 | Index id; |
297 | |
298 | void addImport(Module* curr, Name name, Type params, Type results) { |
299 | auto import = Builder::makeFunction(name, Signature(params, results), {}); |
300 | import->module = ENV; |
301 | import->base = name; |
302 | curr->addFunction(std::move(import)); |
303 | } |
304 | }; |
305 | |
306 | Pass* createInstrumentMemoryPass() { return new InstrumentMemory(); } |
307 | |
308 | } // namespace wasm |
309 | |