WasmStubs.h (11619B)
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 jit::Register gpr_; 37 jit::Register64 gpr64_; 38 jit::FloatRegister fpr_; 39 uint32_t stackOffset_; 40 }; 41 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::Ref: 60 MOZ_ASSERT(loc_ == Location::Gpr); 61 break; 62 case ValType::V128: 63 MOZ_ASSERT(loc_ == Location::Fpr); 64 break; 65 } 66 #endif 67 } 68 69 friend class ABIResultIter; 70 ABIResult() {} 71 72 public: 73 // Sizes of items in the stack area. 74 // 75 // The size values come from the implementations of Push() in 76 // MacroAssembler-x86-shared.cpp and MacroAssembler-arm-shared.cpp, and from 77 // VFPRegister::size() in Architecture-arm.h. 78 // 79 // On ARM unlike on x86 we push a single for float. 80 81 static constexpr size_t StackSizeOfPtr = sizeof(intptr_t); 82 static constexpr size_t StackSizeOfInt32 = StackSizeOfPtr; 83 static constexpr size_t StackSizeOfInt64 = sizeof(int64_t); 84 #if defined(JS_CODEGEN_ARM) 85 static constexpr size_t StackSizeOfFloat = sizeof(float); 86 #else 87 static constexpr size_t StackSizeOfFloat = sizeof(double); 88 #endif 89 static constexpr size_t StackSizeOfDouble = sizeof(double); 90 #ifdef ENABLE_WASM_SIMD 91 static constexpr size_t StackSizeOfV128 = sizeof(V128); 92 #endif 93 94 ABIResult(ValType type, jit::Register gpr) 95 : type_(type), loc_(Location::Gpr), gpr_(gpr) { 96 validate(); 97 } 98 ABIResult(ValType type, jit::Register64 gpr64) 99 : type_(type), loc_(Location::Gpr64), gpr64_(gpr64) { 100 validate(); 101 } 102 ABIResult(ValType type, jit::FloatRegister fpr) 103 : type_(type), loc_(Location::Fpr), fpr_(fpr) { 104 validate(); 105 } 106 ABIResult(ValType type, uint32_t stackOffset) 107 : type_(type), loc_(Location::Stack), stackOffset_(stackOffset) { 108 validate(); 109 } 110 111 ValType type() const { return type_; } 112 bool onStack() const { return loc_ == Location::Stack; } 113 bool inRegister() const { return !onStack(); } 114 jit::Register gpr() const { 115 MOZ_ASSERT(loc_ == Location::Gpr); 116 return gpr_; 117 } 118 jit::Register64 gpr64() const { 119 MOZ_ASSERT(loc_ == Location::Gpr64); 120 return gpr64_; 121 } 122 jit::FloatRegister fpr() const { 123 MOZ_ASSERT(loc_ == Location::Fpr); 124 return fpr_; 125 } 126 // Offset from SP. 127 uint32_t stackOffset() const { 128 MOZ_ASSERT(loc_ == Location::Stack); 129 return stackOffset_; 130 } 131 uint32_t size() const; 132 }; 133 134 // Just as WebAssembly functions can take multiple arguments, they can also 135 // return multiple results. As with a call, a limited number of results will be 136 // located in registers, and the rest will be stored in a stack area. The 137 // |ABIResultIter| computes result locations, given a |ResultType|. 138 // 139 // Recall that a |ResultType| represents a sequence of value types t1..tN, 140 // indexed from 1 to N. In principle it doesn't matter how we decide which 141 // results get to be in registers and which go to the stack. To better 142 // harmonize with WebAssembly's abstract stack machine, whose properties are 143 // taken advantage of by the baseline compiler, our strategy is to start 144 // allocating result locations in "reverse" order: from result N down to 1. 145 // 146 // If a result with index I is in a register, then all results with index J > I 147 // are also in registers. If a result I is on the stack, then all results with 148 // index K < I are also on the stack, farther away from the stack pointer than 149 // result I. 150 // 151 // Currently only a single result is ever stored in a register, though this may 152 // change in the future on register-rich platforms. 153 // 154 // NB: The baseline compiler also uses thie ABI for locations of block 155 // parameters and return values, within individual WebAssembly functions. 156 157 class ABIResultIter { 158 ResultType type_; 159 uint32_t count_; 160 uint32_t index_; 161 uint32_t nextStackOffset_; 162 enum { Next, Prev } direction_; 163 ABIResult cur_; 164 165 void settleRegister(ValType type); 166 void settleNext(); 167 void settlePrev(); 168 169 public: 170 explicit ABIResultIter(const ResultType& type) 171 : type_(type), count_(type.length()) { 172 reset(); 173 } 174 175 void reset() { 176 index_ = nextStackOffset_ = 0; 177 direction_ = Next; 178 if (!done()) { 179 settleNext(); 180 } 181 } 182 bool done() const { return index_ == count_; } 183 uint32_t index() const { return index_; } 184 uint32_t count() const { return count_; } 185 uint32_t remaining() const { return count_ - index_; } 186 void switchToNext() { 187 MOZ_ASSERT(direction_ == Prev); 188 if (!done() && cur().onStack()) { 189 nextStackOffset_ += cur().size(); 190 } 191 index_ = count_ - index_; 192 direction_ = Next; 193 if (!done()) { 194 settleNext(); 195 } 196 } 197 void switchToPrev() { 198 MOZ_ASSERT(direction_ == Next); 199 if (!done() && cur().onStack()) { 200 nextStackOffset_ -= cur().size(); 201 } 202 index_ = count_ - index_; 203 direction_ = Prev; 204 if (!done()) settlePrev(); 205 } 206 void next() { 207 MOZ_ASSERT(direction_ == Next); 208 MOZ_ASSERT(!done()); 209 index_++; 210 if (!done()) { 211 settleNext(); 212 } 213 } 214 void prev() { 215 MOZ_ASSERT(direction_ == Prev); 216 MOZ_ASSERT(!done()); 217 index_++; 218 if (!done()) { 219 settlePrev(); 220 } 221 } 222 const ABIResult& cur() const { 223 MOZ_ASSERT(!done()); 224 return cur_; 225 } 226 227 uint32_t stackBytesConsumedSoFar() const { return nextStackOffset_; } 228 229 static inline bool HasStackResults(const ResultType& type) { 230 return type.length() > MaxRegisterResults; 231 } 232 233 static uint32_t MeasureStackBytes(const ResultType& type) { 234 if (!HasStackResults(type)) { 235 return 0; 236 } 237 ABIResultIter iter(type); 238 while (!iter.done()) { 239 iter.next(); 240 } 241 return iter.stackBytesConsumedSoFar(); 242 } 243 }; 244 245 extern bool GenerateBuiltinThunk(jit::MacroAssembler& masm, 246 jit::ABIFunctionType abiType, 247 bool switchToMainStack, ExitReason exitReason, 248 void* funcPtr, CallableOffsets* offsets); 249 250 extern bool GenerateStubs(const CodeMetadata& codeMeta, 251 const FuncImportVector& imports, 252 const FuncExportVector& exports, CompiledCode* code); 253 254 extern bool GenerateEntryStubs(const CodeMetadata& codeMeta, 255 const FuncExportVector& exports, 256 CompiledCode* code); 257 258 extern bool GenerateEntryStubs(jit::MacroAssembler& masm, 259 size_t funcExportIndex, const FuncExport& fe, 260 const FuncType& funcType, 261 const mozilla::Maybe<jit::ImmPtr>& callee, 262 bool isAsmJS, CodeRangeVector* codeRanges); 263 264 extern void GenerateTrapExitRegisterOffsets(jit::RegisterOffsets* offsets, 265 size_t* numWords); 266 267 extern bool GenerateProvisionalLazyJitEntryStub(jit::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_; 306 U() {} 307 } arg; 308 309 public: 310 JitCallStackArg() : tag_(Tag::Undefined) {} 311 explicit JitCallStackArg(int32_t imm32) : tag_(Tag::Imm32) { 312 arg.imm32_ = imm32; 313 } 314 explicit JitCallStackArg(jit::Register gpr) : tag_(Tag::GPR) { 315 arg.gpr_ = gpr; 316 } 317 explicit JitCallStackArg(jit::FloatRegister fpu) : tag_(Tag::FPU) { 318 new (&arg) jit::FloatRegister(fpu); 319 } 320 explicit JitCallStackArg(const jit::Address& addr) : tag_(Tag::Address) { 321 new (&arg) jit::Address(addr); 322 } 323 324 Tag tag() const { return tag_; } 325 int32_t imm32() const { 326 MOZ_ASSERT(tag_ == Tag::Imm32); 327 return arg.imm32_; 328 } 329 jit::Register gpr() const { 330 MOZ_ASSERT(tag_ == Tag::GPR); 331 return arg.gpr_; 332 } 333 jit::FloatRegister fpu() const { 334 MOZ_ASSERT(tag_ == Tag::FPU); 335 return arg.fpu_; 336 } 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(jit::MacroAssembler& masm, 357 const FuncExport& fe, 358 const Instance& inst, 359 const JitCallStackArgVector& stackArgs, 360 jit::Register scratch, 361 uint32_t* callOffset); 362 363 extern void GenerateJumpToCatchHandler(jit::MacroAssembler& masm, 364 jit::Register rfe, 365 jit::Register scratch1, 366 jit::Register scratch2); 367 368 } // namespace wasm 369 } // namespace js 370 371 #endif // wasm_stubs_h