1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  *
4  * Copyright 2015 Mozilla Foundation
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 #ifndef wasm_stubs_h
20 #define wasm_stubs_h
21 
22 #include "wasm/WasmFrameIter.h"  // js::wasm::ExitReason
23 #include "wasm/WasmGenerator.h"
24 #include "wasm/WasmOpIter.h"
25 
26 namespace js {
27 namespace wasm {
28 
29 // ValType and location for a single result: either in a register or on the
30 // stack.
31 
32 class ABIResult {
33   ValType type_;
34   enum class Location { Gpr, Gpr64, Fpr, Stack } loc_;
35   union {
36     Register gpr_;
37     Register64 gpr64_;
38     FloatRegister fpr_;
39     uint32_t stackOffset_;
40   };
41 
validate()42   void validate() {
43 #ifdef DEBUG
44     if (onStack()) {
45       return;
46     }
47     MOZ_ASSERT(inRegister());
48     switch (type_.kind()) {
49       case ValType::I32:
50         MOZ_ASSERT(loc_ == Location::Gpr);
51         break;
52       case ValType::I64:
53         MOZ_ASSERT(loc_ == Location::Gpr64);
54         break;
55       case ValType::F32:
56       case ValType::F64:
57         MOZ_ASSERT(loc_ == Location::Fpr);
58         break;
59       case ValType::Rtt:
60       case ValType::Ref:
61         MOZ_ASSERT(loc_ == Location::Gpr);
62         break;
63       case ValType::V128:
64         MOZ_ASSERT(loc_ == Location::Fpr);
65         break;
66     }
67 #endif
68   }
69 
70   friend class ABIResultIter;
ABIResult()71   ABIResult() {}
72 
73  public:
74   // Sizes of items in the stack area.
75   //
76   // The size values come from the implementations of Push() in
77   // MacroAssembler-x86-shared.cpp and MacroAssembler-arm-shared.cpp, and from
78   // VFPRegister::size() in Architecture-arm.h.
79   //
80   // On ARM unlike on x86 we push a single for float.
81 
82   static constexpr size_t StackSizeOfPtr = sizeof(intptr_t);
83   static constexpr size_t StackSizeOfInt32 = StackSizeOfPtr;
84   static constexpr size_t StackSizeOfInt64 = sizeof(int64_t);
85 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
86   static constexpr size_t StackSizeOfFloat = sizeof(float);
87 #else
88   static constexpr size_t StackSizeOfFloat = sizeof(double);
89 #endif
90   static constexpr size_t StackSizeOfDouble = sizeof(double);
91 #ifdef ENABLE_WASM_SIMD
92   static constexpr size_t StackSizeOfV128 = sizeof(V128);
93 #endif
94 
ABIResult(ValType type,Register gpr)95   ABIResult(ValType type, Register gpr)
96       : type_(type), loc_(Location::Gpr), gpr_(gpr) {
97     validate();
98   }
ABIResult(ValType type,Register64 gpr64)99   ABIResult(ValType type, Register64 gpr64)
100       : type_(type), loc_(Location::Gpr64), gpr64_(gpr64) {
101     validate();
102   }
ABIResult(ValType type,FloatRegister fpr)103   ABIResult(ValType type, FloatRegister fpr)
104       : type_(type), loc_(Location::Fpr), fpr_(fpr) {
105     validate();
106   }
ABIResult(ValType type,uint32_t stackOffset)107   ABIResult(ValType type, uint32_t stackOffset)
108       : type_(type), loc_(Location::Stack), stackOffset_(stackOffset) {
109     validate();
110   }
111 
type()112   ValType type() const { return type_; }
onStack()113   bool onStack() const { return loc_ == Location::Stack; }
inRegister()114   bool inRegister() const { return !onStack(); }
gpr()115   Register gpr() const {
116     MOZ_ASSERT(loc_ == Location::Gpr);
117     return gpr_;
118   }
gpr64()119   Register64 gpr64() const {
120     MOZ_ASSERT(loc_ == Location::Gpr64);
121     return gpr64_;
122   }
fpr()123   FloatRegister fpr() const {
124     MOZ_ASSERT(loc_ == Location::Fpr);
125     return fpr_;
126   }
127   // Offset from SP.
stackOffset()128   uint32_t stackOffset() const {
129     MOZ_ASSERT(loc_ == Location::Stack);
130     return stackOffset_;
131   }
132   uint32_t size() const;
133 };
134 
135 // Just as WebAssembly functions can take multiple arguments, they can also
136 // return multiple results.  As with a call, a limited number of results will be
137 // located in registers, and the rest will be stored in a stack area.  The
138 // |ABIResultIter| computes result locations, given a |ResultType|.
139 //
140 // Recall that a |ResultType| represents a sequence of value types t1..tN,
141 // indexed from 1 to N.  In principle it doesn't matter how we decide which
142 // results get to be in registers and which go to the stack.  To better
143 // harmonize with WebAssembly's abstract stack machine, whose properties are
144 // taken advantage of by the baseline compiler, our strategy is to start
145 // allocating result locations in "reverse" order: from result N down to 1.
146 //
147 // If a result with index I is in a register, then all results with index J > I
148 // are also in registers.  If a result I is on the stack, then all results with
149 // index K < I are also on the stack, farther away from the stack pointer than
150 // result I.
151 //
152 // Currently only a single result is ever stored in a register, though this may
153 // change in the future on register-rich platforms.
154 //
155 // NB: The baseline compiler also uses thie ABI for locations of block
156 // parameters and return values, within individual WebAssembly functions.
157 
158 class ABIResultIter {
159   ResultType type_;
160   uint32_t count_;
161   uint32_t index_;
162   uint32_t nextStackOffset_;
163   enum { Next, Prev } direction_;
164   ABIResult cur_;
165 
166   void settleRegister(ValType type);
167   void settleNext();
168   void settlePrev();
169 
170  public:
ABIResultIter(const ResultType & type)171   explicit ABIResultIter(const ResultType& type)
172       : type_(type), count_(type.length()) {
173     reset();
174   }
175 
reset()176   void reset() {
177     index_ = nextStackOffset_ = 0;
178     direction_ = Next;
179     if (!done()) {
180       settleNext();
181     }
182   }
done()183   bool done() const { return index_ == count_; }
index()184   uint32_t index() const { return index_; }
count()185   uint32_t count() const { return count_; }
remaining()186   uint32_t remaining() const { return count_ - index_; }
switchToNext()187   void switchToNext() {
188     MOZ_ASSERT(direction_ == Prev);
189     if (!done() && cur().onStack()) {
190       nextStackOffset_ += cur().size();
191     }
192     index_ = count_ - index_;
193     direction_ = Next;
194     if (!done()) {
195       settleNext();
196     }
197   }
switchToPrev()198   void switchToPrev() {
199     MOZ_ASSERT(direction_ == Next);
200     if (!done() && cur().onStack()) {
201       nextStackOffset_ -= cur().size();
202     }
203     index_ = count_ - index_;
204     direction_ = Prev;
205     if (!done()) settlePrev();
206   }
next()207   void next() {
208     MOZ_ASSERT(direction_ == Next);
209     MOZ_ASSERT(!done());
210     index_++;
211     if (!done()) {
212       settleNext();
213     }
214   }
prev()215   void prev() {
216     MOZ_ASSERT(direction_ == Prev);
217     MOZ_ASSERT(!done());
218     index_++;
219     if (!done()) {
220       settlePrev();
221     }
222   }
cur()223   const ABIResult& cur() const {
224     MOZ_ASSERT(!done());
225     return cur_;
226   }
227 
stackBytesConsumedSoFar()228   uint32_t stackBytesConsumedSoFar() const { return nextStackOffset_; }
229 
HasStackResults(const ResultType & type)230   static inline bool HasStackResults(const ResultType& type) {
231     return type.length() > MaxRegisterResults;
232   }
233 
MeasureStackBytes(const ResultType & type)234   static uint32_t MeasureStackBytes(const ResultType& type) {
235     if (!HasStackResults(type)) {
236       return 0;
237     }
238     ABIResultIter iter(type);
239     while (!iter.done()) {
240       iter.next();
241     }
242     return iter.stackBytesConsumedSoFar();
243   }
244 };
245 
246 extern bool GenerateBuiltinThunk(jit::MacroAssembler& masm,
247                                  jit::ABIFunctionType abiType,
248                                  ExitReason exitReason, void* funcPtr,
249                                  CallableOffsets* offsets);
250 
251 extern bool GenerateImportFunctions(const ModuleEnvironment& env,
252                                     const FuncImportVector& imports,
253                                     CompiledCode* code);
254 
255 extern bool GenerateStubs(const ModuleEnvironment& env,
256                           const FuncImportVector& imports,
257                           const FuncExportVector& exports, CompiledCode* code);
258 
259 extern bool GenerateEntryStubs(jit::MacroAssembler& masm,
260                                size_t funcExportIndex, const FuncExport& fe,
261                                const Maybe<jit::ImmPtr>& callee, bool isAsmJS,
262                                CodeRangeVector* codeRanges);
263 
264 extern void GenerateTrapExitMachineState(jit::MachineState* machine,
265                                          size_t* numWords);
266 
267 extern bool GenerateProvisionalLazyJitEntryStub(MacroAssembler& masm,
268                                                 Offsets* offsets);
269 
270 // A value that is written into the trap exit frame, which is useful for
271 // cross-checking during garbage collection.
272 static constexpr uintptr_t TrapExitDummyValue = 1337;
273 
274 // And its offset, in words, down from the highest-addressed word of the trap
275 // exit frame.  The value is written into the frame using WasmPush.  In the
276 // case where WasmPush allocates more than one word, the value will therefore
277 // be written at the lowest-addressed word.
278 #ifdef JS_CODEGEN_ARM64
279 static constexpr size_t TrapExitDummyValueOffsetFromTop = 1;
280 #else
281 static constexpr size_t TrapExitDummyValueOffsetFromTop = 0;
282 #endif
283 
284 // An argument that will end up on the stack according to the system ABI, to be
285 // passed to GenerateDirectCallFromJit. Since the direct JIT call creates its
286 // own frame, it is its responsibility to put stack arguments to their expected
287 // locations; so the caller of GenerateDirectCallFromJit can put them anywhere.
288 
289 class JitCallStackArg {
290  public:
291   enum class Tag {
292     Imm32,
293     GPR,
294     FPU,
295     Address,
296     Undefined,
297   };
298 
299  private:
300   Tag tag_;
301   union U {
302     int32_t imm32_;
303     jit::Register gpr_;
304     jit::FloatRegister fpu_;
305     jit::Address addr_;
U()306     U() {}
307   } arg;
308 
309  public:
JitCallStackArg()310   JitCallStackArg() : tag_(Tag::Undefined) {}
JitCallStackArg(int32_t imm32)311   explicit JitCallStackArg(int32_t imm32) : tag_(Tag::Imm32) {
312     arg.imm32_ = imm32;
313   }
JitCallStackArg(jit::Register gpr)314   explicit JitCallStackArg(jit::Register gpr) : tag_(Tag::GPR) {
315     arg.gpr_ = gpr;
316   }
JitCallStackArg(jit::FloatRegister fpu)317   explicit JitCallStackArg(jit::FloatRegister fpu) : tag_(Tag::FPU) {
318     new (&arg) jit::FloatRegister(fpu);
319   }
JitCallStackArg(const jit::Address & addr)320   explicit JitCallStackArg(const jit::Address& addr) : tag_(Tag::Address) {
321     new (&arg) jit::Address(addr);
322   }
323 
tag()324   Tag tag() const { return tag_; }
imm32()325   int32_t imm32() const {
326     MOZ_ASSERT(tag_ == Tag::Imm32);
327     return arg.imm32_;
328   }
gpr()329   jit::Register gpr() const {
330     MOZ_ASSERT(tag_ == Tag::GPR);
331     return arg.gpr_;
332   }
fpu()333   jit::FloatRegister fpu() const {
334     MOZ_ASSERT(tag_ == Tag::FPU);
335     return arg.fpu_;
336   }
addr()337   const jit::Address& addr() const {
338     MOZ_ASSERT(tag_ == Tag::Address);
339     return arg.addr_;
340   }
341 };
342 
343 using JitCallStackArgVector = Vector<JitCallStackArg, 4, SystemAllocPolicy>;
344 
345 // Generates an inline wasm call (during jit compilation) to a specific wasm
346 // function (as specifed by the given FuncExport).
347 // This call doesn't go through a wasm entry, but rather creates its own
348 // inlined exit frame.
349 // Assumes:
350 // - all the registers have been preserved by the caller,
351 // - all arguments passed in registers have been set up at the expected
352 //   locations,
353 // - all arguments passed on stack slot are alive as defined by a corresponding
354 //   JitCallStackArg.
355 
356 extern void GenerateDirectCallFromJit(
357     jit::MacroAssembler& masm, const FuncExport& fe, const Instance& inst,
358     const JitCallStackArgVector& stackArgs, bool profilingEnabled,
359     jit::Register scratch, uint32_t* callOffset);
360 
361 }  // namespace wasm
362 }  // namespace js
363 
364 #endif  // wasm_stubs_h
365