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