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