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 // Lowers unaligned loads and stores into aligned loads and stores
19 // that are smaller. This leaves only aligned operations.
20 //
21 
22 #include "ir/bits.h"
23 #include "pass.h"
24 #include "wasm-builder.h"
25 #include "wasm.h"
26 
27 namespace wasm {
28 
29 struct AlignmentLowering : public WalkerPass<PostWalker<AlignmentLowering>> {
30   // Core lowering of a 32-bit load: ensures it is done using aligned
31   // operations, which means we can leave it alone if it's already aligned, or
32   // else we break it up into smaller loads that are.
lowerLoadI32wasm::AlignmentLowering33   Expression* lowerLoadI32(Load* curr) {
34     if (curr->align == 0 || curr->align == curr->bytes) {
35       return curr;
36     }
37     auto indexType = getModule()->memory.indexType;
38     Builder builder(*getModule());
39     assert(curr->type == Type::i32);
40     auto temp = builder.addVar(getFunction(), indexType);
41     Expression* ret;
42     if (curr->bytes == 2) {
43       ret = builder.makeBinary(
44         OrInt32,
45         builder.makeLoad(1,
46                          false,
47                          curr->offset,
48                          1,
49                          builder.makeLocalGet(temp, indexType),
50                          Type::i32),
51         builder.makeBinary(
52           ShlInt32,
53           builder.makeLoad(1,
54                            false,
55                            curr->offset + 1,
56                            1,
57                            builder.makeLocalGet(temp, indexType),
58                            Type::i32),
59           builder.makeConst(int32_t(8))));
60       if (curr->signed_) {
61         ret = Bits::makeSignExt(ret, 2, *getModule());
62       }
63     } else if (curr->bytes == 4) {
64       if (curr->align == 1) {
65         ret = builder.makeBinary(
66           OrInt32,
67           builder.makeBinary(
68             OrInt32,
69             builder.makeLoad(1,
70                              false,
71                              curr->offset,
72                              1,
73                              builder.makeLocalGet(temp, indexType),
74                              Type::i32),
75             builder.makeBinary(
76               ShlInt32,
77               builder.makeLoad(1,
78                                false,
79                                curr->offset + 1,
80                                1,
81                                builder.makeLocalGet(temp, indexType),
82                                Type::i32),
83               builder.makeConst(int32_t(8)))),
84           builder.makeBinary(
85             OrInt32,
86             builder.makeBinary(
87               ShlInt32,
88               builder.makeLoad(1,
89                                false,
90                                curr->offset + 2,
91                                1,
92                                builder.makeLocalGet(temp, indexType),
93                                Type::i32),
94               builder.makeConst(int32_t(16))),
95             builder.makeBinary(
96               ShlInt32,
97               builder.makeLoad(1,
98                                false,
99                                curr->offset + 3,
100                                1,
101                                builder.makeLocalGet(temp, indexType),
102                                Type::i32),
103               builder.makeConst(int32_t(24)))));
104       } else if (curr->align == 2) {
105         ret = builder.makeBinary(
106           OrInt32,
107           builder.makeLoad(2,
108                            false,
109                            curr->offset,
110                            2,
111                            builder.makeLocalGet(temp, indexType),
112                            Type::i32),
113           builder.makeBinary(
114             ShlInt32,
115             builder.makeLoad(2,
116                              false,
117                              curr->offset + 2,
118                              2,
119                              builder.makeLocalGet(temp, indexType),
120                              Type::i32),
121             builder.makeConst(int32_t(16))));
122       } else {
123         WASM_UNREACHABLE("invalid alignment");
124       }
125     } else {
126       WASM_UNREACHABLE("invalid size");
127     }
128     return builder.makeBlock({builder.makeLocalSet(temp, curr->ptr), ret});
129   }
130 
131   // Core lowering of a 32-bit store.
lowerStoreI32wasm::AlignmentLowering132   Expression* lowerStoreI32(Store* curr) {
133     if (curr->align == 0 || curr->align == curr->bytes) {
134       return curr;
135     }
136     Builder builder(*getModule());
137     assert(curr->value->type == Type::i32);
138     auto indexType = getModule()->memory.indexType;
139     auto tempPtr = builder.addVar(getFunction(), indexType);
140     auto tempValue = builder.addVar(getFunction(), Type::i32);
141     auto* block =
142       builder.makeBlock({builder.makeLocalSet(tempPtr, curr->ptr),
143                          builder.makeLocalSet(tempValue, curr->value)});
144     if (curr->bytes == 2) {
145       block->list.push_back(
146         builder.makeStore(1,
147                           curr->offset,
148                           1,
149                           builder.makeLocalGet(tempPtr, indexType),
150                           builder.makeLocalGet(tempValue, Type::i32),
151                           Type::i32));
152       block->list.push_back(builder.makeStore(
153         1,
154         curr->offset + 1,
155         1,
156         builder.makeLocalGet(tempPtr, indexType),
157         builder.makeBinary(ShrUInt32,
158                            builder.makeLocalGet(tempValue, Type::i32),
159                            builder.makeConst(int32_t(8))),
160         Type::i32));
161     } else if (curr->bytes == 4) {
162       if (curr->align == 1) {
163         block->list.push_back(
164           builder.makeStore(1,
165                             curr->offset,
166                             1,
167                             builder.makeLocalGet(tempPtr, indexType),
168                             builder.makeLocalGet(tempValue, Type::i32),
169                             Type::i32));
170         block->list.push_back(builder.makeStore(
171           1,
172           curr->offset + 1,
173           1,
174           builder.makeLocalGet(tempPtr, indexType),
175           builder.makeBinary(ShrUInt32,
176                              builder.makeLocalGet(tempValue, Type::i32),
177                              builder.makeConst(int32_t(8))),
178           Type::i32));
179         block->list.push_back(builder.makeStore(
180           1,
181           curr->offset + 2,
182           1,
183           builder.makeLocalGet(tempPtr, indexType),
184           builder.makeBinary(ShrUInt32,
185                              builder.makeLocalGet(tempValue, Type::i32),
186                              builder.makeConst(int32_t(16))),
187           Type::i32));
188         block->list.push_back(builder.makeStore(
189           1,
190           curr->offset + 3,
191           1,
192           builder.makeLocalGet(tempPtr, indexType),
193           builder.makeBinary(ShrUInt32,
194                              builder.makeLocalGet(tempValue, Type::i32),
195                              builder.makeConst(int32_t(24))),
196           Type::i32));
197       } else if (curr->align == 2) {
198         block->list.push_back(
199           builder.makeStore(2,
200                             curr->offset,
201                             2,
202                             builder.makeLocalGet(tempPtr, indexType),
203                             builder.makeLocalGet(tempValue, Type::i32),
204                             Type::i32));
205         block->list.push_back(builder.makeStore(
206           2,
207           curr->offset + 2,
208           2,
209           builder.makeLocalGet(tempPtr, indexType),
210           builder.makeBinary(ShrUInt32,
211                              builder.makeLocalGet(tempValue, Type::i32),
212                              builder.makeConst(int32_t(16))),
213           Type::i32));
214       } else {
215         WASM_UNREACHABLE("invalid alignment");
216       }
217     } else {
218       WASM_UNREACHABLE("invalid size");
219     }
220     block->finalize();
221     return block;
222   }
223 
visitLoadwasm::AlignmentLowering224   void visitLoad(Load* curr) {
225     // If unreachable, just remove the load, which removes the unaligned
226     // operation in a trivial way.
227     if (curr->type == Type::unreachable) {
228       replaceCurrent(curr->ptr);
229       return;
230     }
231     if (curr->align == 0 || curr->align == curr->bytes) {
232       // Nothing to do: leave the node unchanged. All code lower down assumes
233       // the operation is unaligned.
234       return;
235     }
236     Builder builder(*getModule());
237     auto type = curr->type.getBasic();
238     Expression* replacement;
239     switch (type) {
240       default:
241         WASM_UNREACHABLE("unhandled unaligned load");
242       case Type::i32:
243         replacement = lowerLoadI32(curr);
244         break;
245       case Type::f32:
246         curr->type = Type::i32;
247         replacement = builder.makeUnary(ReinterpretInt32, lowerLoadI32(curr));
248         break;
249       case Type::i64:
250       case Type::f64:
251         if (type == Type::i64 && curr->bytes != 8) {
252           // A load of <64 bits.
253           curr->type = Type::i32;
254           replacement = builder.makeUnary(
255             curr->signed_ ? ExtendSInt32 : ExtendUInt32, lowerLoadI32(curr));
256           break;
257         }
258         // Load two 32-bit pieces, and combine them.
259         auto indexType = getModule()->memory.indexType;
260         auto temp = builder.addVar(getFunction(), indexType);
261         auto* set = builder.makeLocalSet(temp, curr->ptr);
262         Expression* low =
263           lowerLoadI32(builder.makeLoad(4,
264                                         false,
265                                         curr->offset,
266                                         curr->align,
267                                         builder.makeLocalGet(temp, indexType),
268                                         Type::i32));
269         low = builder.makeUnary(ExtendUInt32, low);
270         // Note that the alignment is assumed to be the same here, even though
271         // we add an offset of 4. That is because this is an unaligned load, so
272         // the alignment is 1, 2, or 4, which means it stays the same after
273         // adding 4.
274         Expression* high =
275           lowerLoadI32(builder.makeLoad(4,
276                                         false,
277                                         curr->offset + 4,
278                                         curr->align,
279                                         builder.makeLocalGet(temp, indexType),
280                                         Type::i32));
281         high = builder.makeUnary(ExtendUInt32, high);
282         high =
283           builder.makeBinary(ShlInt64, high, builder.makeConst(int64_t(32)));
284         auto* combined = builder.makeBinary(OrInt64, low, high);
285         replacement = builder.makeSequence(set, combined);
286         // Ensure the proper output type.
287         if (type == Type::f64) {
288           replacement = builder.makeUnary(ReinterpretInt64, replacement);
289         }
290         break;
291     }
292     replaceCurrent(replacement);
293   }
294 
visitStorewasm::AlignmentLowering295   void visitStore(Store* curr) {
296     Builder builder(*getModule());
297     // If unreachable, just remove the store, which removes the unaligned
298     // operation in a trivial way.
299     if (curr->type == Type::unreachable) {
300       replaceCurrent(builder.makeBlock(
301         {builder.makeDrop(curr->ptr), builder.makeDrop(curr->value)}));
302       return;
303     }
304     if (curr->align == 0 || curr->align == curr->bytes) {
305       // Nothing to do: leave the node unchanged. All code lower down assumes
306       // the operation is unaligned.
307       return;
308     }
309     auto type = curr->value->type.getBasic();
310     Expression* replacement;
311     switch (type) {
312       default:
313         WASM_UNREACHABLE("unhandled unaligned store");
314       case Type::i32:
315         replacement = lowerStoreI32(curr);
316         break;
317       case Type::f32:
318         curr->type = Type::i32;
319         curr->value = builder.makeUnary(ReinterpretFloat32, curr->value);
320         replacement = lowerStoreI32(curr);
321         break;
322       case Type::i64:
323       case Type::f64:
324         if (type == Type::i64 && curr->bytes != 8) {
325           // A store of <64 bits.
326           curr->type = Type::i32;
327           curr->value = builder.makeUnary(WrapInt64, curr->value);
328           replacement = lowerStoreI32(curr);
329           break;
330         }
331         // Otherwise, fall through to f64 case for a 64-bit load.
332         // Ensure an integer input value.
333         auto* value = curr->value;
334         if (type == Type::f64) {
335           value = builder.makeUnary(ReinterpretFloat64, value);
336         }
337         // Store as two 32-bit pieces.
338         auto indexType = getModule()->memory.indexType;
339         auto tempPtr = builder.addVar(getFunction(), indexType);
340         auto* setPtr = builder.makeLocalSet(tempPtr, curr->ptr);
341         auto tempValue = builder.addVar(getFunction(), Type::i64);
342         auto* setValue = builder.makeLocalSet(tempValue, value);
343         Expression* low = builder.makeUnary(
344           WrapInt64, builder.makeLocalGet(tempValue, Type::i64));
345         low = lowerStoreI32(
346           builder.makeStore(4,
347                             curr->offset,
348                             curr->align,
349                             builder.makeLocalGet(tempPtr, indexType),
350                             low,
351                             Type::i32));
352         Expression* high =
353           builder.makeBinary(ShrUInt64,
354                              builder.makeLocalGet(tempValue, Type::i64),
355                              builder.makeConst(int64_t(32)));
356         high = builder.makeUnary(WrapInt64, high);
357         // Note that the alignment is assumed to be the same here, even though
358         // we add an offset of 4. That is because this is an unaligned store, so
359         // the alignment is 1, 2, or 4, which means it stays the same after
360         // adding 4.
361         high = lowerStoreI32(
362           builder.makeStore(4,
363                             curr->offset + 4,
364                             curr->align,
365                             builder.makeLocalGet(tempPtr, indexType),
366                             high,
367                             Type::i32));
368         replacement = builder.makeBlock({setPtr, setValue, low, high});
369         break;
370     }
371     replaceCurrent(replacement);
372   }
373 };
374 
createAlignmentLoweringPass()375 Pass* createAlignmentLoweringPass() { return new AlignmentLowering(); }
376 
377 } // namespace wasm
378