1/*
2 * Copyright (C) 2016-2017 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#pragma once
27
28#if ENABLE(B3_JIT)
29
30#include "AirCode.h"
31#include "AirGenerationContext.h"
32#include "AirInst.h"
33#include "AirSpecial.h"
34#include "B3ValueInlines.h"
35#include "B3WasmBoundsCheckValue.h"
36
37namespace JSC { namespace B3 { namespace Air {
38
39// This defines the behavior of custom instructions - i.e. those whose behavior cannot be
40// described using AirOpcode.opcodes. If you define an opcode as "custom Foo" in that file, then
41// you will need to create a "struct FooCustom" here that implements the custom behavior
42// methods.
43//
44// The customizability granted by the custom instruction mechanism is strictly less than what
45// you get using the Patch instruction and implementing a Special. However, that path requires
46// allocating a Special object and ensuring that it's the first operand. For many instructions,
47// that is not as convenient as using Custom, which makes the instruction look like any other
48// instruction. Note that both of those extra powers of the Patch instruction happen because we
49// special-case that instruction in many phases and analyses. Non-special-cased behaviors of
50// Patch are implemented using the custom instruction mechanism.
51//
52// Specials are still more flexible if you need to list extra clobbered registers and you'd like
53// that to be expressed as a bitvector rather than an arglist. They are also more flexible if
54// you need to carry extra state around with the instruction. Also, Specials mean that you
55// always have access to Code& even in methods that don't take a GenerationContext.
56
57// Definition of Patch instruction. Patch is used to delegate the behavior of the instruction to the
58// Special object, which will be the first argument to the instruction.
59struct PatchCustom {
60 static void forEachArg(Inst& inst, ScopedLambda<Inst::EachArgCallback> lambda)
61 {
62 // This is basically bogus, but it works for analyses that model Special as an
63 // immediate.
64 lambda(inst.args[0], Arg::Use, GP, pointerWidth());
65
66 inst.args[0].special()->forEachArg(inst, lambda);
67 }
68
69 template<typename... Arguments>
70 static bool isValidFormStatic(Arguments...)
71 {
72 return false;
73 }
74
75 static bool isValidForm(Inst& inst);
76
77 static bool admitsStack(Inst& inst, unsigned argIndex)
78 {
79 if (!argIndex)
80 return false;
81 return inst.args[0].special()->admitsStack(inst, argIndex);
82 }
83
84 static bool admitsExtendedOffsetAddr(Inst& inst, unsigned argIndex)
85 {
86 if (!argIndex)
87 return false;
88 return inst.args[0].special()->admitsExtendedOffsetAddr(inst, argIndex);
89 }
90
91 static Optional<unsigned> shouldTryAliasingDef(Inst& inst)
92 {
93 return inst.args[0].special()->shouldTryAliasingDef(inst);
94 }
95
96 static bool isTerminal(Inst& inst)
97 {
98 return inst.args[0].special()->isTerminal(inst);
99 }
100
101 static bool hasNonArgEffects(Inst& inst)
102 {
103 return inst.args[0].special()->hasNonArgEffects(inst);
104 }
105
106 static bool hasNonArgNonControlEffects(Inst& inst)
107 {
108 return inst.args[0].special()->hasNonArgNonControlEffects(inst);
109 }
110
111 static CCallHelpers::Jump generate(
112 Inst& inst, CCallHelpers& jit, GenerationContext& context)
113 {
114 return inst.args[0].special()->generate(inst, jit, context);
115 }
116};
117
118template<typename Subtype>
119struct CommonCustomBase {
120 static bool hasNonArgEffects(Inst& inst)
121 {
122 return Subtype::isTerminal(inst) || Subtype::hasNonArgNonControlEffects(inst);
123 }
124};
125
126// Definition of CCall instruction. CCall is used for hot path C function calls. It's lowered to a
127// Patch with an Air CCallSpecial along with code to marshal instructions. The lowering happens
128// before register allocation, so that the register allocator sees the clobbers.
129struct CCallCustom : public CommonCustomBase<CCallCustom> {
130 template<typename Functor>
131 static void forEachArg(Inst& inst, const Functor& functor)
132 {
133 Value* value = inst.origin;
134
135 unsigned index = 0;
136
137 functor(inst.args[index++], Arg::Use, GP, pointerWidth()); // callee
138
139 if (value->type() != Void) {
140 functor(
141 inst.args[index++], Arg::Def,
142 bankForType(value->type()),
143 widthForType(value->type()));
144 }
145
146 for (unsigned i = 1; i < value->numChildren(); ++i) {
147 Value* child = value->child(i);
148 functor(
149 inst.args[index++], Arg::Use,
150 bankForType(child->type()),
151 widthForType(child->type()));
152 }
153 }
154
155 template<typename... Arguments>
156 static bool isValidFormStatic(Arguments...)
157 {
158 return false;
159 }
160
161 static bool isValidForm(Inst&);
162
163 static bool admitsStack(Inst&, unsigned)
164 {
165 return true;
166 }
167
168 static bool admitsExtendedOffsetAddr(Inst&, unsigned)
169 {
170 return false;
171 }
172
173 static bool isTerminal(Inst&)
174 {
175 return false;
176 }
177
178 static bool hasNonArgNonControlEffects(Inst&)
179 {
180 return true;
181 }
182
183 // This just crashes, since we expect C calls to be lowered before generation.
184 static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&);
185};
186
187struct ColdCCallCustom : CCallCustom {
188 template<typename Functor>
189 static void forEachArg(Inst& inst, const Functor& functor)
190 {
191 // This is just like a call, but uses become cold.
192 CCallCustom::forEachArg(
193 inst,
194 [&] (Arg& arg, Arg::Role role, Bank bank, Width width) {
195 functor(arg, Arg::cooled(role), bank, width);
196 });
197 }
198};
199
200struct ShuffleCustom : public CommonCustomBase<ShuffleCustom> {
201 template<typename Functor>
202 static void forEachArg(Inst& inst, const Functor& functor)
203 {
204 unsigned limit = inst.args.size() / 3 * 3;
205 for (unsigned i = 0; i < limit; i += 3) {
206 Arg& src = inst.args[i + 0];
207 Arg& dst = inst.args[i + 1];
208 Arg& widthArg = inst.args[i + 2];
209 Width width = widthArg.width();
210 Bank bank = src.isGP() && dst.isGP() ? GP : FP;
211 functor(src, Arg::Use, bank, width);
212 functor(dst, Arg::Def, bank, width);
213 functor(widthArg, Arg::Use, GP, Width8);
214 }
215 }
216
217 template<typename... Arguments>
218 static bool isValidFormStatic(Arguments...)
219 {
220 return false;
221 }
222
223 static bool isValidForm(Inst&);
224
225 static bool admitsStack(Inst&, unsigned index)
226 {
227 switch (index % 3) {
228 case 0:
229 case 1:
230 return true;
231 default:
232 return false;
233 }
234 }
235
236 static bool admitsExtendedOffsetAddr(Inst&, unsigned)
237 {
238 return false;
239 }
240
241 static bool isTerminal(Inst&)
242 {
243 return false;
244 }
245
246 static bool hasNonArgNonControlEffects(Inst&)
247 {
248 return false;
249 }
250
251 static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&);
252};
253
254struct EntrySwitchCustom : public CommonCustomBase<EntrySwitchCustom> {
255 template<typename Func>
256 static void forEachArg(Inst&, const Func&)
257 {
258 }
259
260 template<typename... Arguments>
261 static bool isValidFormStatic(Arguments...)
262 {
263 return !sizeof...(Arguments);
264 }
265
266 static bool isValidForm(Inst& inst)
267 {
268 return inst.args.isEmpty();
269 }
270
271 static bool admitsStack(Inst&, unsigned)
272 {
273 return false;
274 }
275
276 static bool admitsExtendedOffsetAddr(Inst&, unsigned)
277 {
278 return false;
279 }
280
281 static bool isTerminal(Inst&)
282 {
283 return true;
284 }
285
286 static bool hasNonArgNonControlEffects(Inst&)
287 {
288 return false;
289 }
290
291 static CCallHelpers::Jump generate(Inst&, CCallHelpers&, GenerationContext&)
292 {
293 // This should never be reached because we should have lowered EntrySwitch before
294 // generation.
295 UNREACHABLE_FOR_PLATFORM();
296 return CCallHelpers::Jump();
297 }
298};
299
300struct WasmBoundsCheckCustom : public CommonCustomBase<WasmBoundsCheckCustom> {
301 template<typename Func>
302 static void forEachArg(Inst& inst, const Func& functor)
303 {
304 functor(inst.args[0], Arg::Use, GP, Width64);
305 functor(inst.args[1], Arg::Use, GP, Width64);
306 }
307
308 template<typename... Arguments>
309 static bool isValidFormStatic(Arguments...)
310 {
311 return false;
312 }
313
314 static bool isValidForm(Inst&);
315
316 static bool admitsStack(Inst&, unsigned)
317 {
318 return false;
319 }
320
321 static bool admitsExtendedOffsetAddr(Inst&, unsigned)
322 {
323 return false;
324 }
325
326 static bool isTerminal(Inst&)
327 {
328 return false;
329 }
330
331 static bool hasNonArgNonControlEffects(Inst&)
332 {
333 return true;
334 }
335
336 static CCallHelpers::Jump generate(Inst& inst, CCallHelpers& jit, GenerationContext& context)
337 {
338 WasmBoundsCheckValue* value = inst.origin->as<WasmBoundsCheckValue>();
339 CCallHelpers::Jump outOfBounds = Inst(Air::Branch64, value, Arg::relCond(CCallHelpers::AboveOrEqual), inst.args[0], inst.args[1]).generate(jit, context);
340
341 context.latePaths.append(createSharedTask<GenerationContext::LatePathFunction>(
342 [outOfBounds, value] (CCallHelpers& jit, Air::GenerationContext& context) {
343 outOfBounds.link(&jit);
344 switch (value->boundsType()) {
345 case WasmBoundsCheckValue::Type::Pinned:
346 context.code->wasmBoundsCheckGenerator()->run(jit, value->bounds().pinnedSize);
347 break;
348
349 case WasmBoundsCheckValue::Type::Maximum:
350 context.code->wasmBoundsCheckGenerator()->run(jit, InvalidGPRReg);
351 break;
352 }
353 }));
354
355 // We said we were not a terminal.
356 return CCallHelpers::Jump();
357 }
358};
359
360} } } // namespace JSC::B3::Air
361
362#endif // ENABLE(B3_JIT)
363