1 | /*
|
---|
2 | * Copyright (C) 2015-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 "CallFrameShuffler.h"
|
---|
28 |
|
---|
29 | #if ENABLE(JIT) && USE(JSVALUE64)
|
---|
30 |
|
---|
31 | #include "CCallHelpers.h"
|
---|
32 | #include "DataFormat.h"
|
---|
33 | #include "JSCJSValueInlines.h"
|
---|
34 |
|
---|
35 | namespace JSC {
|
---|
36 |
|
---|
37 | DataFormat CallFrameShuffler::emitStore(
|
---|
38 | CachedRecovery& cachedRecovery, MacroAssembler::Address address)
|
---|
39 | {
|
---|
40 | ASSERT(!cachedRecovery.recovery().isInJSStack());
|
---|
41 |
|
---|
42 | switch (cachedRecovery.recovery().technique()) {
|
---|
43 | case InGPR:
|
---|
44 | m_jit.storePtr(cachedRecovery.recovery().gpr(), address);
|
---|
45 | return DataFormatJS;
|
---|
46 | case UnboxedInt32InGPR:
|
---|
47 | m_jit.store32(cachedRecovery.recovery().gpr(), address.withOffset(PayloadOffset));
|
---|
48 | return DataFormatInt32;
|
---|
49 | case UnboxedInt52InGPR:
|
---|
50 | m_jit.rshift64(MacroAssembler::TrustedImm32(JSValue::int52ShiftAmount),
|
---|
51 | cachedRecovery.recovery().gpr());
|
---|
52 | FALLTHROUGH;
|
---|
53 | case UnboxedStrictInt52InGPR:
|
---|
54 | m_jit.storePtr(cachedRecovery.recovery().gpr(), address);
|
---|
55 | return DataFormatStrictInt52;
|
---|
56 | case UnboxedBooleanInGPR:
|
---|
57 | m_jit.storePtr(cachedRecovery.recovery().gpr(), address);
|
---|
58 | return DataFormatBoolean;
|
---|
59 | case UnboxedCellInGPR:
|
---|
60 | m_jit.storePtr(cachedRecovery.recovery().gpr(), address);
|
---|
61 | return DataFormatCell;
|
---|
62 | case UnboxedDoubleInFPR:
|
---|
63 | m_jit.storeDouble(cachedRecovery.recovery().fpr(), address);
|
---|
64 | return DataFormatDouble;
|
---|
65 | case InFPR:
|
---|
66 | m_jit.storeDouble(cachedRecovery.recovery().fpr(), address);
|
---|
67 | return DataFormatJS;
|
---|
68 | case Constant:
|
---|
69 | m_jit.storeTrustedValue(cachedRecovery.recovery().constant(), address);
|
---|
70 | return DataFormatJS;
|
---|
71 | default:
|
---|
72 | RELEASE_ASSERT_NOT_REACHED();
|
---|
73 | }
|
---|
74 | }
|
---|
75 |
|
---|
76 | void CallFrameShuffler::emitBox(CachedRecovery& cachedRecovery)
|
---|
77 | {
|
---|
78 | ASSERT(canBox(cachedRecovery));
|
---|
79 | if (cachedRecovery.recovery().isConstant())
|
---|
80 | return;
|
---|
81 |
|
---|
82 | if (cachedRecovery.recovery().isInGPR()) {
|
---|
83 | switch (cachedRecovery.recovery().dataFormat()) {
|
---|
84 | case DataFormatInt32:
|
---|
85 | if (verbose)
|
---|
86 | dataLog(" * Boxing ", cachedRecovery.recovery());
|
---|
87 | m_jit.zeroExtend32ToWord(
|
---|
88 | cachedRecovery.recovery().gpr(),
|
---|
89 | cachedRecovery.recovery().gpr());
|
---|
90 | m_lockedRegisters.set(cachedRecovery.recovery().gpr());
|
---|
91 | if (tryAcquireNumberTagRegister())
|
---|
92 | m_jit.or64(m_numberTagRegister, cachedRecovery.recovery().gpr());
|
---|
93 | else {
|
---|
94 | // We have to do this the hard way
|
---|
95 | m_jit.or64(MacroAssembler::TrustedImm64(JSValue::NumberTag),
|
---|
96 | cachedRecovery.recovery().gpr());
|
---|
97 | }
|
---|
98 | m_lockedRegisters.clear(cachedRecovery.recovery().gpr());
|
---|
99 | cachedRecovery.setRecovery(
|
---|
100 | ValueRecovery::inGPR(cachedRecovery.recovery().gpr(), DataFormatJS));
|
---|
101 | if (verbose)
|
---|
102 | dataLog(" into ", cachedRecovery.recovery(), "\n");
|
---|
103 | return;
|
---|
104 | case DataFormatInt52:
|
---|
105 | if (verbose)
|
---|
106 | dataLog(" * Boxing ", cachedRecovery.recovery());
|
---|
107 | m_jit.rshift64(MacroAssembler::TrustedImm32(JSValue::int52ShiftAmount),
|
---|
108 | cachedRecovery.recovery().gpr());
|
---|
109 | cachedRecovery.setRecovery(
|
---|
110 | ValueRecovery::inGPR(cachedRecovery.recovery().gpr(), DataFormatStrictInt52));
|
---|
111 | if (verbose)
|
---|
112 | dataLog(" into ", cachedRecovery.recovery(), "\n");
|
---|
113 | FALLTHROUGH;
|
---|
114 | case DataFormatStrictInt52: {
|
---|
115 | if (verbose)
|
---|
116 | dataLog(" * Boxing ", cachedRecovery.recovery());
|
---|
117 | FPRReg resultFPR = getFreeFPR();
|
---|
118 | ASSERT(resultFPR != InvalidFPRReg);
|
---|
119 | m_jit.convertInt64ToDouble(cachedRecovery.recovery().gpr(), resultFPR);
|
---|
120 | updateRecovery(cachedRecovery, ValueRecovery::inFPR(resultFPR, DataFormatDouble));
|
---|
121 | if (verbose)
|
---|
122 | dataLog(" into ", cachedRecovery.recovery(), "\n");
|
---|
123 | break;
|
---|
124 | }
|
---|
125 | case DataFormatBoolean:
|
---|
126 | if (verbose)
|
---|
127 | dataLog(" * Boxing ", cachedRecovery.recovery());
|
---|
128 | m_jit.add32(MacroAssembler::TrustedImm32(JSValue::ValueFalse),
|
---|
129 | cachedRecovery.recovery().gpr());
|
---|
130 | cachedRecovery.setRecovery(
|
---|
131 | ValueRecovery::inGPR(cachedRecovery.recovery().gpr(), DataFormatJS));
|
---|
132 | if (verbose)
|
---|
133 | dataLog(" into ", cachedRecovery.recovery(), "\n");
|
---|
134 | return;
|
---|
135 | default:
|
---|
136 | return;
|
---|
137 | }
|
---|
138 | }
|
---|
139 |
|
---|
140 | if (cachedRecovery.recovery().isInFPR()) {
|
---|
141 | if (cachedRecovery.recovery().dataFormat() == DataFormatDouble) {
|
---|
142 | if (verbose)
|
---|
143 | dataLog(" * Boxing ", cachedRecovery.recovery());
|
---|
144 | GPRReg resultGPR = cachedRecovery.wantedJSValueRegs().gpr();
|
---|
145 | if (resultGPR == InvalidGPRReg || m_registers[resultGPR])
|
---|
146 | resultGPR = getFreeGPR();
|
---|
147 | ASSERT(resultGPR != InvalidGPRReg);
|
---|
148 | m_jit.purifyNaN(cachedRecovery.recovery().fpr());
|
---|
149 | m_jit.moveDoubleTo64(cachedRecovery.recovery().fpr(), resultGPR);
|
---|
150 | m_lockedRegisters.set(resultGPR);
|
---|
151 | if (tryAcquireNumberTagRegister())
|
---|
152 | m_jit.sub64(m_numberTagRegister, resultGPR);
|
---|
153 | else
|
---|
154 | m_jit.sub64(MacroAssembler::TrustedImm64(JSValue::NumberTag), resultGPR);
|
---|
155 | m_lockedRegisters.clear(resultGPR);
|
---|
156 | updateRecovery(cachedRecovery, ValueRecovery::inGPR(resultGPR, DataFormatJS));
|
---|
157 | if (verbose)
|
---|
158 | dataLog(" into ", cachedRecovery.recovery(), "\n");
|
---|
159 | return;
|
---|
160 | }
|
---|
161 | ASSERT(cachedRecovery.recovery().dataFormat() == DataFormatJS);
|
---|
162 | return;
|
---|
163 | }
|
---|
164 |
|
---|
165 | RELEASE_ASSERT_NOT_REACHED();
|
---|
166 | }
|
---|
167 |
|
---|
168 | void CallFrameShuffler::emitLoad(CachedRecovery& cachedRecovery)
|
---|
169 | {
|
---|
170 | if (!cachedRecovery.recovery().isInJSStack())
|
---|
171 | return;
|
---|
172 |
|
---|
173 | if (verbose)
|
---|
174 | dataLog(" * Loading ", cachedRecovery.recovery(), " into ");
|
---|
175 |
|
---|
176 | VirtualRegister reg = cachedRecovery.recovery().virtualRegister();
|
---|
177 | MacroAssembler::Address address { addressForOld(reg) };
|
---|
178 | bool tryFPR { true };
|
---|
179 | GPRReg resultGPR { cachedRecovery.wantedJSValueRegs().gpr() };
|
---|
180 |
|
---|
181 | // If we want a GPR and it's available, that's better than loading
|
---|
182 | // into an FPR.
|
---|
183 | if (resultGPR != InvalidGPRReg && !m_registers[resultGPR]
|
---|
184 | && !m_lockedRegisters.get(resultGPR) && cachedRecovery.loadsIntoGPR())
|
---|
185 | tryFPR = false;
|
---|
186 |
|
---|
187 | // Otherwise, we prefer loading into FPRs if possible
|
---|
188 | if (tryFPR && cachedRecovery.loadsIntoFPR()) {
|
---|
189 | FPRReg resultFPR { cachedRecovery.wantedFPR() };
|
---|
190 | if (resultFPR == InvalidFPRReg || m_registers[resultFPR] || m_lockedRegisters.get(resultFPR))
|
---|
191 | resultFPR = getFreeFPR();
|
---|
192 | if (resultFPR != InvalidFPRReg) {
|
---|
193 | m_jit.loadDouble(address, resultFPR);
|
---|
194 | DataFormat dataFormat = DataFormatJS;
|
---|
195 | // We could be transforming a DataFormatCell into a
|
---|
196 | // DataFormatJS here - but that's OK.
|
---|
197 | if (cachedRecovery.recovery().dataFormat() == DataFormatDouble)
|
---|
198 | dataFormat = DataFormatDouble;
|
---|
199 | updateRecovery(cachedRecovery,
|
---|
200 | ValueRecovery::inFPR(resultFPR, dataFormat));
|
---|
201 | if (verbose)
|
---|
202 | dataLog(cachedRecovery.recovery(), "\n");
|
---|
203 | if (reg == newAsOld(dangerFrontier()))
|
---|
204 | updateDangerFrontier();
|
---|
205 | return;
|
---|
206 | }
|
---|
207 | }
|
---|
208 |
|
---|
209 | ASSERT(cachedRecovery.loadsIntoGPR());
|
---|
210 | if (resultGPR == InvalidGPRReg || m_registers[resultGPR] || m_lockedRegisters.get(resultGPR))
|
---|
211 | resultGPR = getFreeGPR();
|
---|
212 | ASSERT(resultGPR != InvalidGPRReg);
|
---|
213 | m_jit.loadPtr(address, resultGPR);
|
---|
214 | updateRecovery(cachedRecovery,
|
---|
215 | ValueRecovery::inGPR(resultGPR, cachedRecovery.recovery().dataFormat()));
|
---|
216 | if (verbose)
|
---|
217 | dataLog(cachedRecovery.recovery(), "\n");
|
---|
218 | if (reg == newAsOld(dangerFrontier()))
|
---|
219 | updateDangerFrontier();
|
---|
220 | }
|
---|
221 |
|
---|
222 | bool CallFrameShuffler::canLoad(CachedRecovery& cachedRecovery)
|
---|
223 | {
|
---|
224 | if (!cachedRecovery.recovery().isInJSStack())
|
---|
225 | return true;
|
---|
226 |
|
---|
227 | ASSERT(cachedRecovery.loadsIntoFPR() || cachedRecovery.loadsIntoGPR());
|
---|
228 |
|
---|
229 | if (cachedRecovery.loadsIntoFPR() && getFreeFPR() != InvalidFPRReg)
|
---|
230 | return true;
|
---|
231 |
|
---|
232 | if (cachedRecovery.loadsIntoGPR() && getFreeGPR() != InvalidGPRReg)
|
---|
233 | return true;
|
---|
234 |
|
---|
235 | return false;
|
---|
236 | }
|
---|
237 |
|
---|
238 | void CallFrameShuffler::emitDisplace(CachedRecovery& cachedRecovery)
|
---|
239 | {
|
---|
240 | Reg wantedReg;
|
---|
241 | if (!(wantedReg = Reg { cachedRecovery.wantedJSValueRegs().gpr() }))
|
---|
242 | wantedReg = Reg { cachedRecovery.wantedFPR() };
|
---|
243 | ASSERT(wantedReg);
|
---|
244 | ASSERT(!m_lockedRegisters.get(wantedReg));
|
---|
245 |
|
---|
246 | if (CachedRecovery* current = m_registers[wantedReg]) {
|
---|
247 | if (current == &cachedRecovery) {
|
---|
248 | if (verbose)
|
---|
249 | dataLog(" + ", wantedReg, " is OK\n");
|
---|
250 | return;
|
---|
251 | }
|
---|
252 | // We could do a more complex thing by finding cycles
|
---|
253 | // etc. in that case.
|
---|
254 | // However, ending up in this situation will be super
|
---|
255 | // rare, and should actually be outright impossible for
|
---|
256 | // non-FTL tiers, since:
|
---|
257 | // (a) All doubles have been converted into JSValues with
|
---|
258 | // ValueRep nodes, so FPRs are initially free
|
---|
259 | //
|
---|
260 | // (b) The only recoveries with wanted registers are the
|
---|
261 | // callee (which always starts out in a register) and
|
---|
262 | // the callee-save registers
|
---|
263 | //
|
---|
264 | // (c) The callee-save registers are the first things we
|
---|
265 | // load (after the return PC), and they are loaded as JSValues
|
---|
266 | //
|
---|
267 | // (d) We prefer loading JSValues into FPRs if their
|
---|
268 | // wanted GPR is not available
|
---|
269 | //
|
---|
270 | // (e) If we end up spilling some registers with a
|
---|
271 | // target, we won't load them again before the very
|
---|
272 | // end of the algorithm
|
---|
273 | //
|
---|
274 | // Combined, this means that we will never load a recovery
|
---|
275 | // with a wanted GPR into any GPR other than its wanted
|
---|
276 | // GPR. The callee could however have been initially in
|
---|
277 | // one of the callee-save registers - but since the wanted
|
---|
278 | // GPR for the callee is always regT0, it will be the
|
---|
279 | // first one to be displaced, and we won't see it when
|
---|
280 | // handling any of the callee-save registers.
|
---|
281 | //
|
---|
282 | // Thus, the only way we could ever reach this path is in
|
---|
283 | // the FTL, when there is so much pressure that we
|
---|
284 | // absolutely need to load the callee-save registers into
|
---|
285 | // different GPRs initially but not enough pressure to
|
---|
286 | // then have to spill all of them. And even in that case,
|
---|
287 | // depending on the order in which B3 saves the
|
---|
288 | // callee-saves, we will probably still be safe. Anyway,
|
---|
289 | // the couple extra move instructions compared to an
|
---|
290 | // efficient cycle-based algorithm are not going to hurt
|
---|
291 | // us.
|
---|
292 | if (wantedReg.isFPR()) {
|
---|
293 | FPRReg tempFPR = getFreeFPR();
|
---|
294 | if (verbose)
|
---|
295 | dataLog(" * Moving ", wantedReg, " into ", tempFPR, "\n");
|
---|
296 | m_jit.moveDouble(wantedReg.fpr(), tempFPR);
|
---|
297 | updateRecovery(*current,
|
---|
298 | ValueRecovery::inFPR(tempFPR, current->recovery().dataFormat()));
|
---|
299 | } else {
|
---|
300 | GPRReg tempGPR = getFreeGPR();
|
---|
301 | if (verbose)
|
---|
302 | dataLog(" * Moving ", wantedReg.gpr(), " into ", tempGPR, "\n");
|
---|
303 | m_jit.move(wantedReg.gpr(), tempGPR);
|
---|
304 | updateRecovery(*current,
|
---|
305 | ValueRecovery::inGPR(tempGPR, current->recovery().dataFormat()));
|
---|
306 | }
|
---|
307 | }
|
---|
308 | ASSERT(!m_registers[wantedReg]);
|
---|
309 |
|
---|
310 | if (cachedRecovery.recovery().isConstant()) {
|
---|
311 | // We only care about callee saves for wanted FPRs, and those are never constants
|
---|
312 | ASSERT(wantedReg.isGPR());
|
---|
313 | if (verbose)
|
---|
314 | dataLog(" * Loading ", cachedRecovery.recovery().constant(), " into ", wantedReg, "\n");
|
---|
315 | m_jit.moveTrustedValue(cachedRecovery.recovery().constant(), JSValueRegs { wantedReg.gpr() });
|
---|
316 | updateRecovery(
|
---|
317 | cachedRecovery,
|
---|
318 | ValueRecovery::inRegister(wantedReg, DataFormatJS));
|
---|
319 | } else if (cachedRecovery.recovery().isInGPR()) {
|
---|
320 | if (verbose)
|
---|
321 | dataLog(" * Moving ", cachedRecovery.recovery(), " into ", wantedReg, "\n");
|
---|
322 | if (wantedReg.isGPR())
|
---|
323 | m_jit.move(cachedRecovery.recovery().gpr(), wantedReg.gpr());
|
---|
324 | else
|
---|
325 | m_jit.move64ToDouble(cachedRecovery.recovery().gpr(), wantedReg.fpr());
|
---|
326 | RELEASE_ASSERT(cachedRecovery.recovery().dataFormat() == DataFormatJS);
|
---|
327 | updateRecovery(cachedRecovery,
|
---|
328 | ValueRecovery::inRegister(wantedReg, DataFormatJS));
|
---|
329 | } else {
|
---|
330 | ASSERT(cachedRecovery.recovery().isInFPR());
|
---|
331 | if (cachedRecovery.recovery().dataFormat() == DataFormatDouble) {
|
---|
332 | // We only care about callee saves for wanted FPRs, and those are always DataFormatJS
|
---|
333 | ASSERT(wantedReg.isGPR());
|
---|
334 | // This will automatically pick the wanted GPR
|
---|
335 | emitBox(cachedRecovery);
|
---|
336 | } else {
|
---|
337 | if (verbose)
|
---|
338 | dataLog(" * Moving ", cachedRecovery.recovery().fpr(), " into ", wantedReg, "\n");
|
---|
339 | if (wantedReg.isGPR())
|
---|
340 | m_jit.moveDoubleTo64(cachedRecovery.recovery().fpr(), wantedReg.gpr());
|
---|
341 | else
|
---|
342 | m_jit.moveDouble(cachedRecovery.recovery().fpr(), wantedReg.fpr());
|
---|
343 | RELEASE_ASSERT(cachedRecovery.recovery().dataFormat() == DataFormatJS);
|
---|
344 | updateRecovery(cachedRecovery,
|
---|
345 | ValueRecovery::inRegister(wantedReg, DataFormatJS));
|
---|
346 | }
|
---|
347 | }
|
---|
348 |
|
---|
349 | ASSERT(m_registers[wantedReg] == &cachedRecovery);
|
---|
350 | }
|
---|
351 |
|
---|
352 | bool CallFrameShuffler::tryAcquireNumberTagRegister()
|
---|
353 | {
|
---|
354 | if (m_numberTagRegister != InvalidGPRReg)
|
---|
355 | return true;
|
---|
356 |
|
---|
357 | m_numberTagRegister = getFreeGPR();
|
---|
358 |
|
---|
359 | if (m_numberTagRegister == InvalidGPRReg)
|
---|
360 | return false;
|
---|
361 |
|
---|
362 | m_lockedRegisters.set(m_numberTagRegister);
|
---|
363 | m_jit.move(MacroAssembler::TrustedImm64(JSValue::NumberTag), m_numberTagRegister);
|
---|
364 | return true;
|
---|
365 | }
|
---|
366 |
|
---|
367 | } // namespace JSC
|
---|
368 |
|
---|
369 | #endif // ENABLE(JIT) && USE(JSVALUE64)
|
---|