1 | /* |
2 | * Copyright (C) 2016-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "AirFixObviousSpills.h" |
28 | |
29 | #if ENABLE(B3_JIT) |
30 | |
31 | #include "AirArgInlines.h" |
32 | #include "AirCode.h" |
33 | #include "AirInstInlines.h" |
34 | #include "AirPhaseScope.h" |
35 | #include <wtf/IndexMap.h> |
36 | #include <wtf/ListDump.h> |
37 | |
38 | namespace JSC { namespace B3 { namespace Air { |
39 | |
40 | namespace { |
41 | |
42 | namespace AirFixObviousSpillsInternal { |
43 | static constexpr bool verbose = false; |
44 | } |
45 | |
46 | class FixObviousSpills { |
47 | public: |
48 | FixObviousSpills(Code& code) |
49 | : m_code(code) |
50 | , m_atHead(code.size()) |
51 | { |
52 | } |
53 | |
54 | void run() |
55 | { |
56 | if (AirFixObviousSpillsInternal::verbose) |
57 | dataLog("Code before fixObviousSpills:\n" , m_code); |
58 | |
59 | computeAliases(); |
60 | fixCode(); |
61 | } |
62 | |
63 | private: |
64 | void computeAliases() |
65 | { |
66 | m_atHead[m_code[0]].wasVisited = true; |
67 | |
68 | bool changed = true; |
69 | while (changed) { |
70 | changed = false; |
71 | |
72 | for (BasicBlock* block : m_code) { |
73 | m_block = block; |
74 | m_state = m_atHead[block]; |
75 | if (!m_state.wasVisited) |
76 | continue; |
77 | |
78 | if (AirFixObviousSpillsInternal::verbose) |
79 | dataLog("Executing block " , *m_block, ": " , m_state, "\n" ); |
80 | |
81 | for (m_instIndex = 0; m_instIndex < block->size(); ++m_instIndex) |
82 | executeInst(); |
83 | |
84 | for (BasicBlock* successor : block->successorBlocks()) { |
85 | State& toState = m_atHead[successor]; |
86 | if (toState.wasVisited) |
87 | changed |= toState.merge(m_state); |
88 | else { |
89 | toState = m_state; |
90 | changed = true; |
91 | } |
92 | } |
93 | } |
94 | } |
95 | } |
96 | |
97 | void fixCode() |
98 | { |
99 | for (BasicBlock* block : m_code) { |
100 | m_block = block; |
101 | m_state = m_atHead[block]; |
102 | RELEASE_ASSERT(m_state.wasVisited); |
103 | |
104 | for (m_instIndex = 0; m_instIndex < block->size(); ++m_instIndex) { |
105 | fixInst(); |
106 | executeInst(); |
107 | } |
108 | } |
109 | } |
110 | |
111 | template<typename Func> |
112 | void forAllAliases(const Func& func) |
113 | { |
114 | Inst& inst = m_block->at(m_instIndex); |
115 | |
116 | switch (inst.kind.opcode) { |
117 | case Move: |
118 | if (inst.args[0].isSomeImm()) { |
119 | if (inst.args[1].isReg()) |
120 | func(RegConst(inst.args[1].reg(), inst.args[0].value())); |
121 | else if (isSpillSlot(inst.args[1])) |
122 | func(SlotConst(inst.args[1].stackSlot(), inst.args[0].value())); |
123 | } else if (isSpillSlot(inst.args[0]) && inst.args[1].isReg()) { |
124 | if (Optional<int64_t> constant = m_state.constantFor(inst.args[0])) |
125 | func(RegConst(inst.args[1].reg(), *constant)); |
126 | func(RegSlot(inst.args[1].reg(), inst.args[0].stackSlot(), RegSlot::AllBits)); |
127 | } else if (inst.args[0].isReg() && isSpillSlot(inst.args[1])) { |
128 | if (Optional<int64_t> constant = m_state.constantFor(inst.args[0])) |
129 | func(SlotConst(inst.args[1].stackSlot(), *constant)); |
130 | func(RegSlot(inst.args[0].reg(), inst.args[1].stackSlot(), RegSlot::AllBits)); |
131 | } |
132 | break; |
133 | |
134 | case Move32: |
135 | if (inst.args[0].isSomeImm()) { |
136 | if (inst.args[1].isReg()) |
137 | func(RegConst(inst.args[1].reg(), static_cast<uint32_t>(inst.args[0].value()))); |
138 | else if (isSpillSlot(inst.args[1])) |
139 | func(SlotConst(inst.args[1].stackSlot(), static_cast<uint32_t>(inst.args[0].value()))); |
140 | } else if (isSpillSlot(inst.args[0]) && inst.args[1].isReg()) { |
141 | if (Optional<int64_t> constant = m_state.constantFor(inst.args[0])) |
142 | func(RegConst(inst.args[1].reg(), static_cast<uint32_t>(*constant))); |
143 | func(RegSlot(inst.args[1].reg(), inst.args[0].stackSlot(), RegSlot::ZExt32)); |
144 | } else if (inst.args[0].isReg() && isSpillSlot(inst.args[1])) { |
145 | if (Optional<int64_t> constant = m_state.constantFor(inst.args[0])) |
146 | func(SlotConst(inst.args[1].stackSlot(), static_cast<int32_t>(*constant))); |
147 | func(RegSlot(inst.args[0].reg(), inst.args[1].stackSlot(), RegSlot::Match32)); |
148 | } |
149 | break; |
150 | |
151 | case MoveFloat: |
152 | if (isSpillSlot(inst.args[0]) && inst.args[1].isReg()) |
153 | func(RegSlot(inst.args[1].reg(), inst.args[0].stackSlot(), RegSlot::Match32)); |
154 | else if (inst.args[0].isReg() && isSpillSlot(inst.args[1])) |
155 | func(RegSlot(inst.args[0].reg(), inst.args[1].stackSlot(), RegSlot::Match32)); |
156 | break; |
157 | |
158 | case MoveDouble: |
159 | if (isSpillSlot(inst.args[0]) && inst.args[1].isReg()) |
160 | func(RegSlot(inst.args[1].reg(), inst.args[0].stackSlot(), RegSlot::AllBits)); |
161 | else if (inst.args[0].isReg() && isSpillSlot(inst.args[1])) |
162 | func(RegSlot(inst.args[0].reg(), inst.args[1].stackSlot(), RegSlot::AllBits)); |
163 | break; |
164 | |
165 | default: |
166 | break; |
167 | } |
168 | } |
169 | |
170 | void executeInst() |
171 | { |
172 | Inst& inst = m_block->at(m_instIndex); |
173 | |
174 | if (AirFixObviousSpillsInternal::verbose) |
175 | dataLog(" Executing " , inst, ": " , m_state, "\n" ); |
176 | |
177 | Inst::forEachDefWithExtraClobberedRegs<Arg>( |
178 | &inst, &inst, |
179 | [&] (const Arg& arg, Arg::Role, Bank, Width) { |
180 | if (AirFixObviousSpillsInternal::verbose) |
181 | dataLog(" Clobbering " , arg, "\n" ); |
182 | m_state.clobber(arg); |
183 | }); |
184 | |
185 | forAllAliases( |
186 | [&] (const auto& alias) { |
187 | m_state.addAlias(alias); |
188 | }); |
189 | } |
190 | |
191 | void fixInst() |
192 | { |
193 | Inst& inst = m_block->at(m_instIndex); |
194 | |
195 | if (AirFixObviousSpillsInternal::verbose) |
196 | dataLog("Fixing inst " , inst, ": " , m_state, "\n" ); |
197 | |
198 | // Check if alias analysis says that this is unnecessary. |
199 | bool shouldLive = true; |
200 | forAllAliases( |
201 | [&] (const auto& alias) { |
202 | shouldLive &= !m_state.contains(alias); |
203 | }); |
204 | if (!shouldLive) { |
205 | inst = Inst(); |
206 | return; |
207 | } |
208 | |
209 | // First handle some special instructions. |
210 | switch (inst.kind.opcode) { |
211 | case Move: { |
212 | if (inst.args[0].isBigImm() && inst.args[1].isReg() |
213 | && isValidForm(Add64, Arg::Imm, Arg::Tmp, Arg::Tmp)) { |
214 | // BigImm materializations are super expensive on both x86 and ARM. Let's try to |
215 | // materialize this bad boy using math instead. Note that we use unsigned math here |
216 | // since it's more deterministic. |
217 | uint64_t myValue = inst.args[0].value(); |
218 | Reg myDest = inst.args[1].reg(); |
219 | for (const RegConst& regConst : m_state.regConst) { |
220 | uint64_t otherValue = regConst.constant; |
221 | |
222 | // Let's try add. That's the only thing that works on all platforms, since it's |
223 | // the only cheap arithmetic op that x86 does in three operands. Long term, we |
224 | // should add fancier materializations here for ARM if the BigImm is yuge. |
225 | uint64_t delta = myValue - otherValue; |
226 | |
227 | if (Arg::isValidImmForm(delta)) { |
228 | inst.kind = Add64; |
229 | inst.args.resize(3); |
230 | inst.args[0] = Arg::imm(delta); |
231 | inst.args[1] = Tmp(regConst.reg); |
232 | inst.args[2] = Tmp(myDest); |
233 | return; |
234 | } |
235 | } |
236 | return; |
237 | } |
238 | break; |
239 | } |
240 | |
241 | default: |
242 | break; |
243 | } |
244 | |
245 | // FIXME: This code should be taught how to simplify the spill-to-spill move |
246 | // instruction. Basically it needs to know to remove the scratch arg. |
247 | // https://bugs.webkit.org/show_bug.cgi?id=171133 |
248 | |
249 | // Create a copy in case we invalidate the instruction. That doesn't happen often. |
250 | Inst instCopy = inst; |
251 | |
252 | // The goal is to replace references to stack slots. We only care about early uses. We can't |
253 | // handle UseDefs. We could teach this to handle UseDefs if we inserted a store instruction |
254 | // after and we proved that the register aliased to the stack slot dies here. We can get that |
255 | // information from the liveness analysis. We also can't handle late uses, because we don't |
256 | // look at late clobbers when doing this. |
257 | bool didThings = false; |
258 | auto handleArg = [&] (Arg& arg, Arg::Role role, Bank, Width width) { |
259 | if (!isSpillSlot(arg)) |
260 | return; |
261 | if (!Arg::isEarlyUse(role)) |
262 | return; |
263 | if (Arg::isAnyDef(role)) |
264 | return; |
265 | |
266 | // Try to get a register if at all possible. |
267 | if (const RegSlot* alias = m_state.getRegSlot(arg.stackSlot())) { |
268 | switch (width) { |
269 | case Width64: |
270 | if (alias->mode != RegSlot::AllBits) |
271 | return; |
272 | if (AirFixObviousSpillsInternal::verbose) |
273 | dataLog(" Replacing " , arg, " with " , alias->reg, "\n" ); |
274 | arg = Tmp(alias->reg); |
275 | didThings = true; |
276 | return; |
277 | case Width32: |
278 | if (AirFixObviousSpillsInternal::verbose) |
279 | dataLog(" Replacing " , arg, " with " , alias->reg, " (subwidth case)\n" ); |
280 | arg = Tmp(alias->reg); |
281 | didThings = true; |
282 | return; |
283 | default: |
284 | return; |
285 | } |
286 | } |
287 | |
288 | // Revert to immediate if that didn't work. |
289 | if (const SlotConst* alias = m_state.getSlotConst(arg.stackSlot())) { |
290 | if (AirFixObviousSpillsInternal::verbose) |
291 | dataLog(" Replacing " , arg, " with constant " , alias->constant, "\n" ); |
292 | if (Arg::isValidImmForm(alias->constant)) |
293 | arg = Arg::imm(alias->constant); |
294 | else |
295 | arg = Arg::bigImm(alias->constant); |
296 | didThings = true; |
297 | return; |
298 | } |
299 | }; |
300 | |
301 | inst.forEachArg(handleArg); |
302 | if (!didThings || inst.isValidForm()) |
303 | return; |
304 | |
305 | // We introduced something invalid along the way. Back up and carefully handle each argument. |
306 | inst = instCopy; |
307 | ASSERT(inst.isValidForm()); |
308 | inst.forEachArg( |
309 | [&] (Arg& arg, Arg::Role role, Bank bank, Width width) { |
310 | Arg argCopy = arg; |
311 | handleArg(arg, role, bank, width); |
312 | if (!inst.isValidForm()) |
313 | arg = argCopy; |
314 | }); |
315 | } |
316 | |
317 | static bool isSpillSlot(const Arg& arg) |
318 | { |
319 | return arg.isStack() && arg.stackSlot()->isSpill(); |
320 | } |
321 | |
322 | struct RegConst { |
323 | RegConst() |
324 | { |
325 | } |
326 | |
327 | RegConst(Reg reg, int64_t constant) |
328 | : reg(reg) |
329 | , constant(constant) |
330 | { |
331 | } |
332 | |
333 | explicit operator bool() const |
334 | { |
335 | return !!reg; |
336 | } |
337 | |
338 | bool operator==(const RegConst& other) const |
339 | { |
340 | return reg == other.reg |
341 | && constant == other.constant; |
342 | } |
343 | |
344 | void dump(PrintStream& out) const |
345 | { |
346 | out.print(reg, "->" , constant); |
347 | } |
348 | |
349 | Reg reg; |
350 | int64_t constant { 0 }; |
351 | }; |
352 | |
353 | struct RegSlot { |
354 | enum Mode : int8_t { |
355 | AllBits, |
356 | ZExt32, // Register contains zero-extended contents of stack slot. |
357 | Match32 // Low 32 bits of register match low 32 bits of stack slot. |
358 | }; |
359 | |
360 | RegSlot() |
361 | { |
362 | } |
363 | |
364 | RegSlot(Reg reg, StackSlot* slot, Mode mode) |
365 | : slot(slot) |
366 | , reg(reg) |
367 | , mode(mode) |
368 | { |
369 | } |
370 | |
371 | explicit operator bool() const |
372 | { |
373 | return slot && reg; |
374 | } |
375 | |
376 | bool operator==(const RegSlot& other) const |
377 | { |
378 | return slot == other.slot |
379 | && reg == other.reg |
380 | && mode == other.mode; |
381 | } |
382 | |
383 | void dump(PrintStream& out) const |
384 | { |
385 | out.print(pointerDump(slot), "->" , reg); |
386 | switch (mode) { |
387 | case AllBits: |
388 | out.print("(AllBits)" ); |
389 | break; |
390 | case ZExt32: |
391 | out.print("(ZExt32)" ); |
392 | break; |
393 | case Match32: |
394 | out.print("(Match32)" ); |
395 | break; |
396 | } |
397 | } |
398 | |
399 | StackSlot* slot { nullptr }; |
400 | Reg reg; |
401 | Mode mode { AllBits }; |
402 | }; |
403 | |
404 | struct SlotConst { |
405 | SlotConst() |
406 | { |
407 | } |
408 | |
409 | SlotConst(StackSlot* slot, int64_t constant) |
410 | : slot(slot) |
411 | , constant(constant) |
412 | { |
413 | } |
414 | |
415 | explicit operator bool() const |
416 | { |
417 | return slot; |
418 | } |
419 | |
420 | bool operator==(const SlotConst& other) const |
421 | { |
422 | return slot == other.slot |
423 | && constant == other.constant; |
424 | } |
425 | |
426 | void dump(PrintStream& out) const |
427 | { |
428 | out.print(pointerDump(slot), "->" , constant); |
429 | } |
430 | |
431 | StackSlot* slot { nullptr }; |
432 | int64_t constant { 0 }; |
433 | }; |
434 | |
435 | struct State { |
436 | void addAlias(const RegConst& newAlias) |
437 | { |
438 | return regConst.append(newAlias); |
439 | } |
440 | void addAlias(const RegSlot& newAlias) |
441 | { |
442 | return regSlot.append(newAlias); |
443 | } |
444 | void addAlias(const SlotConst& newAlias) |
445 | { |
446 | return slotConst.append(newAlias); |
447 | } |
448 | |
449 | bool contains(const RegConst& alias) |
450 | { |
451 | return regConst.contains(alias); |
452 | } |
453 | bool contains(const RegSlot& alias) |
454 | { |
455 | return regSlot.contains(alias); |
456 | } |
457 | bool contains(const SlotConst& alias) |
458 | { |
459 | return slotConst.contains(alias); |
460 | } |
461 | |
462 | const RegConst* getRegConst(Reg reg) const |
463 | { |
464 | for (const RegConst& alias : regConst) { |
465 | if (alias.reg == reg) |
466 | return &alias; |
467 | } |
468 | return nullptr; |
469 | } |
470 | |
471 | const RegSlot* getRegSlot(Reg reg) const |
472 | { |
473 | for (const RegSlot& alias : regSlot) { |
474 | if (alias.reg == reg) |
475 | return &alias; |
476 | } |
477 | return nullptr; |
478 | } |
479 | |
480 | const RegSlot* getRegSlot(StackSlot* slot) const |
481 | { |
482 | for (const RegSlot& alias : regSlot) { |
483 | if (alias.slot == slot) |
484 | return &alias; |
485 | } |
486 | return nullptr; |
487 | } |
488 | |
489 | const RegSlot* getRegSlot(Reg reg, StackSlot* slot) const |
490 | { |
491 | for (const RegSlot& alias : regSlot) { |
492 | if (alias.reg == reg && alias.slot == slot) |
493 | return &alias; |
494 | } |
495 | return nullptr; |
496 | } |
497 | |
498 | const SlotConst* getSlotConst(StackSlot* slot) const |
499 | { |
500 | for (const SlotConst& alias : slotConst) { |
501 | if (alias.slot == slot) |
502 | return &alias; |
503 | } |
504 | return nullptr; |
505 | } |
506 | |
507 | Optional<int64_t> constantFor(const Arg& arg) |
508 | { |
509 | if (arg.isReg()) { |
510 | if (const RegConst* alias = getRegConst(arg.reg())) |
511 | return alias->constant; |
512 | return WTF::nullopt; |
513 | } |
514 | if (arg.isStack()) { |
515 | if (const SlotConst* alias = getSlotConst(arg.stackSlot())) |
516 | return alias->constant; |
517 | return WTF::nullopt; |
518 | } |
519 | return WTF::nullopt; |
520 | } |
521 | |
522 | void clobber(const Arg& arg) |
523 | { |
524 | if (arg.isReg()) { |
525 | regConst.removeAllMatching( |
526 | [&] (const RegConst& alias) -> bool { |
527 | return alias.reg == arg.reg(); |
528 | }); |
529 | regSlot.removeAllMatching( |
530 | [&] (const RegSlot& alias) -> bool { |
531 | return alias.reg == arg.reg(); |
532 | }); |
533 | return; |
534 | } |
535 | if (arg.isStack()) { |
536 | slotConst.removeAllMatching( |
537 | [&] (const SlotConst& alias) -> bool { |
538 | return alias.slot == arg.stackSlot(); |
539 | }); |
540 | regSlot.removeAllMatching( |
541 | [&] (const RegSlot& alias) -> bool { |
542 | return alias.slot == arg.stackSlot(); |
543 | }); |
544 | } |
545 | } |
546 | |
547 | bool merge(const State& other) |
548 | { |
549 | bool changed = false; |
550 | |
551 | changed |= !!regConst.removeAllMatching( |
552 | [&] (RegConst& alias) -> bool { |
553 | const RegConst* otherAlias = other.getRegConst(alias.reg); |
554 | if (!otherAlias) |
555 | return true; |
556 | if (alias.constant != otherAlias->constant) |
557 | return true; |
558 | return false; |
559 | }); |
560 | |
561 | changed |= !!slotConst.removeAllMatching( |
562 | [&] (SlotConst& alias) -> bool { |
563 | const SlotConst* otherAlias = other.getSlotConst(alias.slot); |
564 | if (!otherAlias) |
565 | return true; |
566 | if (alias.constant != otherAlias->constant) |
567 | return true; |
568 | return false; |
569 | }); |
570 | |
571 | changed |= !!regSlot.removeAllMatching( |
572 | [&] (RegSlot& alias) -> bool { |
573 | const RegSlot* otherAlias = other.getRegSlot(alias.reg, alias.slot); |
574 | if (!otherAlias) |
575 | return true; |
576 | if (alias.mode != RegSlot::Match32 && alias.mode != otherAlias->mode) { |
577 | alias.mode = RegSlot::Match32; |
578 | changed = true; |
579 | } |
580 | return false; |
581 | }); |
582 | |
583 | return changed; |
584 | } |
585 | |
586 | void dump(PrintStream& out) const |
587 | { |
588 | out.print( |
589 | "{regConst = [" , listDump(regConst), "], slotConst = [" , listDump(slotConst), |
590 | "], regSlot = [" , listDump(regSlot), "], wasVisited = " , wasVisited, "}" ); |
591 | } |
592 | |
593 | Vector<RegConst> regConst; |
594 | Vector<SlotConst> slotConst; |
595 | Vector<RegSlot> regSlot; |
596 | bool wasVisited { false }; |
597 | }; |
598 | |
599 | Code& m_code; |
600 | IndexMap<BasicBlock*, State> m_atHead; |
601 | State m_state; |
602 | BasicBlock* m_block { nullptr }; |
603 | unsigned m_instIndex { 0 }; |
604 | }; |
605 | |
606 | } // anonymous namespace |
607 | |
608 | void fixObviousSpills(Code& code) |
609 | { |
610 | PhaseScope phaseScope(code, "fixObviousSpills" ); |
611 | |
612 | FixObviousSpills fixObviousSpills(code); |
613 | fixObviousSpills.run(); |
614 | } |
615 | |
616 | } } } // namespace JSC::B3::Air |
617 | |
618 | #endif // ENABLE(B3_JIT) |
619 | |
620 | |