Trampoline.cpp (14524B)
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 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include <initializer_list> 8 9 #include "jit/JitFrames.h" 10 #include "jit/JitRuntime.h" 11 #include "jit/MacroAssembler.h" 12 #include "vm/JitActivation.h" 13 #include "vm/JSContext.h" 14 15 #include "jit/MacroAssembler-inl.h" 16 17 using namespace js; 18 using namespace js::jit; 19 20 void JitRuntime::generateExceptionTailStub(MacroAssembler& masm, 21 Label* profilerExitTail, 22 Label* bailoutTail) { 23 AutoCreatedBy acb(masm, "JitRuntime::generateExceptionTailStub"); 24 25 exceptionTailOffset_ = startTrampolineCode(masm); 26 27 uint32_t returnValueCheckOffset = 0; 28 masm.bind(masm.failureLabel()); 29 masm.handleFailureWithHandlerTail(profilerExitTail, bailoutTail, 30 &returnValueCheckOffset); 31 32 exceptionTailReturnValueCheckOffset_ = returnValueCheckOffset; 33 } 34 35 void JitRuntime::generateProfilerExitFrameTailStub(MacroAssembler& masm, 36 Label* profilerExitTail) { 37 AutoCreatedBy acb(masm, "JitRuntime::generateProfilerExitFrameTailStub"); 38 39 profilerExitFrameTailOffset_ = startTrampolineCode(masm); 40 masm.bind(profilerExitTail); 41 42 static constexpr size_t CallerFPOffset = 43 CommonFrameLayout::offsetOfCallerFramePtr(); 44 45 // Assert the caller frame's type is one of the types we expect. 46 auto emitAssertPrevFrameType = [&masm]( 47 Register framePtr, Register scratch, 48 std::initializer_list<FrameType> types) { 49 #ifdef DEBUG 50 masm.loadPtr(Address(framePtr, CommonFrameLayout::offsetOfDescriptor()), 51 scratch); 52 masm.and32(Imm32(FrameDescriptor::TypeMask), scratch); 53 54 Label checkOk; 55 for (FrameType type : types) { 56 masm.branch32(Assembler::Equal, scratch, Imm32(type), &checkOk); 57 } 58 masm.assumeUnreachable("Unexpected previous frame"); 59 masm.bind(&checkOk); 60 #else 61 (void)masm; 62 #endif 63 }; 64 65 AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); 66 regs.take(JSReturnOperand); 67 Register scratch = regs.takeAny(); 68 69 // The code generated below expects that the current frame pointer points 70 // to an Ion or Baseline frame, at the state it would be immediately before 71 // the frame epilogue and ret(). Thus, after this stub's business is done, it 72 // restores the frame pointer and stack pointer, then executes a ret() and 73 // returns directly to the caller frame, on behalf of the callee script that 74 // jumped to this code. 75 // 76 // Thus the expected state is: 77 // 78 // [JitFrameLayout] <-- FramePointer 79 // [frame contents] <-- StackPointer 80 // 81 // The generated jitcode is responsible for overwriting the 82 // jitActivation->lastProfilingFrame field with a pointer to the previous 83 // Ion or Baseline jit-frame that was pushed before this one. It is also 84 // responsible for overwriting jitActivation->lastProfilingCallSite with 85 // the return address into that frame. 86 // 87 // So this jitcode is responsible for "walking up" the jit stack, finding 88 // the previous Ion or Baseline JS frame, and storing its address and the 89 // return address into the appropriate fields on the current jitActivation. 90 // 91 // There are a fixed number of different path types that can lead to the 92 // current frame, which is either a Baseline or Ion frame: 93 // 94 // <Baseline-Or-Ion> 95 // ^ 96 // | 97 // ^--- Ion (or Baseline JSOp::Resume) 98 // | 99 // ^--- Baseline Stub <---- Baseline 100 // | 101 // ^--- IonICCall <---- Ion 102 // | 103 // ^--- Entry Frame (BaselineInterpreter) (unwrapped) 104 // | 105 // ^--- Trampoline Native (unwrapped) 106 // | 107 // ^--- Entry Frame (CppToJSJit or WasmToJSJit) 108 // 109 // NOTE: Keep this in sync with JSJitProfilingFrameIterator::moveToNextFrame! 110 111 Register actReg = regs.takeAny(); 112 masm.loadJSContext(actReg); 113 masm.loadPtr(Address(actReg, offsetof(JSContext, profilingActivation_)), 114 actReg); 115 116 Address lastProfilingFrame(actReg, 117 JitActivation::offsetOfLastProfilingFrame()); 118 Address lastProfilingCallSite(actReg, 119 JitActivation::offsetOfLastProfilingCallSite()); 120 121 #ifdef DEBUG 122 // Ensure that frame we are exiting is current lastProfilingFrame 123 { 124 masm.loadPtr(lastProfilingFrame, scratch); 125 Label checkOk; 126 masm.branchPtr(Assembler::Equal, scratch, ImmWord(0), &checkOk); 127 masm.branchPtr(Assembler::Equal, FramePointer, scratch, &checkOk); 128 masm.assumeUnreachable( 129 "Mismatch between stored lastProfilingFrame and current frame " 130 "pointer."); 131 masm.bind(&checkOk); 132 } 133 #endif 134 135 // Move FP into a scratch register and use that scratch register below, to 136 // allow unwrapping frames without clobbering FP. 137 Register fpScratch = regs.takeAny(); 138 masm.mov(FramePointer, fpScratch); 139 140 Label again; 141 masm.bind(&again); 142 143 // Load the frame descriptor into |scratch|, figure out what to do depending 144 // on its type. 145 masm.loadPtr(Address(fpScratch, JitFrameLayout::offsetOfDescriptor()), 146 scratch); 147 masm.and32(Imm32(FrameDescriptor::TypeMask), scratch); 148 149 // Handling of each case is dependent on FrameDescriptor.type 150 Label handle_BaselineOrIonJS; 151 Label handle_BaselineStub; 152 Label handle_TrampolineNative; 153 Label handle_BaselineInterpreterEntry; 154 Label handle_IonICCall; 155 Label handle_Entry; 156 157 // We check for IonJS and BaselineStub first because these are the most common 158 // types. Calls from Baseline are usually from a BaselineStub frame. 159 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::IonJS), 160 &handle_BaselineOrIonJS); 161 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::BaselineStub), 162 &handle_BaselineStub); 163 if (JitOptions.emitInterpreterEntryTrampoline) { 164 masm.branch32(Assembler::Equal, scratch, 165 Imm32(FrameType::BaselineInterpreterEntry), 166 &handle_BaselineInterpreterEntry); 167 } 168 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::CppToJSJit), 169 &handle_Entry); 170 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::BaselineJS), 171 &handle_BaselineOrIonJS); 172 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::IonICCall), 173 &handle_IonICCall); 174 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::TrampolineNative), 175 &handle_TrampolineNative); 176 masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::WasmToJSJit), 177 &handle_Entry); 178 179 masm.assumeUnreachable( 180 "Invalid caller frame type when returning from a JIT frame."); 181 182 masm.bind(&handle_BaselineOrIonJS); 183 { 184 // Returning directly to a Baseline or Ion frame. 185 186 // lastProfilingCallSite := ReturnAddress 187 masm.loadPtr(Address(fpScratch, JitFrameLayout::offsetOfReturnAddress()), 188 scratch); 189 masm.storePtr(scratch, lastProfilingCallSite); 190 191 // lastProfilingFrame := CallerFrame 192 masm.loadPtr(Address(fpScratch, CallerFPOffset), scratch); 193 masm.storePtr(scratch, lastProfilingFrame); 194 195 masm.moveToStackPtr(FramePointer); 196 masm.pop(FramePointer); 197 masm.ret(); 198 } 199 200 // Shared implementation for BaselineStub and IonICCall frames. 201 auto emitHandleStubFrame = [&](FrameType expectedPrevType) { 202 // Load pointer to stub frame and assert type of its caller frame. 203 masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); 204 emitAssertPrevFrameType(fpScratch, scratch, {expectedPrevType}); 205 206 // lastProfilingCallSite := StubFrame.ReturnAddress 207 masm.loadPtr(Address(fpScratch, CommonFrameLayout::offsetOfReturnAddress()), 208 scratch); 209 masm.storePtr(scratch, lastProfilingCallSite); 210 211 // lastProfilingFrame := StubFrame.CallerFrame 212 masm.loadPtr(Address(fpScratch, CallerFPOffset), scratch); 213 masm.storePtr(scratch, lastProfilingFrame); 214 215 masm.moveToStackPtr(FramePointer); 216 masm.pop(FramePointer); 217 masm.ret(); 218 }; 219 220 masm.bind(&handle_BaselineStub); 221 { 222 // BaselineJS => BaselineStub frame. 223 emitHandleStubFrame(FrameType::BaselineJS); 224 } 225 226 masm.bind(&handle_IonICCall); 227 { 228 // IonJS => IonICCall frame. 229 emitHandleStubFrame(FrameType::IonJS); 230 } 231 232 masm.bind(&handle_TrampolineNative); 233 { 234 // There can be multiple previous frame types so just "unwrap" this frame 235 // and try again. 236 masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); 237 emitAssertPrevFrameType(fpScratch, scratch, 238 {FrameType::IonJS, FrameType::BaselineStub, 239 FrameType::CppToJSJit, FrameType::WasmToJSJit}); 240 masm.jump(&again); 241 } 242 243 if (JitOptions.emitInterpreterEntryTrampoline) { 244 masm.bind(&handle_BaselineInterpreterEntry); 245 { 246 // Unwrap the baseline interpreter entry frame and try again. 247 masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); 248 emitAssertPrevFrameType(fpScratch, scratch, 249 {FrameType::IonJS, FrameType::BaselineJS, 250 FrameType::BaselineStub, FrameType::CppToJSJit, 251 FrameType::WasmToJSJit, FrameType::IonICCall}); 252 masm.jump(&again); 253 } 254 } 255 256 masm.bind(&handle_Entry); 257 { 258 // FrameType::CppToJSJit / FrameType::WasmToJSJit 259 // 260 // A fast-path wasm->jit transition frame is an entry frame from the point 261 // of view of the JIT. 262 // Store null into both fields. 263 masm.movePtr(ImmPtr(nullptr), scratch); 264 masm.storePtr(scratch, lastProfilingCallSite); 265 masm.storePtr(scratch, lastProfilingFrame); 266 267 masm.moveToStackPtr(FramePointer); 268 masm.pop(FramePointer); 269 masm.ret(); 270 } 271 } 272 273 #ifndef JS_CODEGEN_ARM64 274 // This is a shared path used by generateEnterJit on all architectures 275 // except arm64, which has its own implementation that avoids using the 276 // pseudo stack pointer. 277 void JitRuntime::generateEnterJitShared(MacroAssembler& masm, Register argcReg, 278 Register argvReg, 279 Register calleeTokenReg, 280 Register scratch, Register scratch2, 281 Register scratch3) { 282 // Preconditions: 283 // - argcReg contains the number of actual args passed (not including this). 284 // - argvReg points to the beginning of an array of argcReg argument values, 285 // *not including* `this`. `this` is at argvReg[-1]. If newTarget exists, 286 // it follows the last argument at argvReg[argcReg]. 287 // - calleeTokenReg contains the calleeToken, still tagged. 288 // - no alignment is assumed. 289 // 290 // Postconditions: 291 // - stack padding has been inserted if necessary for alignment. 292 // - if necessary, newTarget has been copied into place. 293 // - if calleeToken is a function and argc < fun->nargs(), `undefined` values 294 // have been pushed to make up the difference. 295 // - the arguments have been pushed to the stack. 296 // - the callee token has been pushed 297 // - the descriptor has *not* been pushed, because x86 doesn't have enough 298 // registers available to pass in actualArgs. 299 static_assert( 300 sizeof(JitFrameLayout) % JitStackAlignment == 0, 301 "No need to consider the JitFrameLayout for aligning the stack"); 302 303 Label notFunction, doneArgs; 304 masm.branchTest32(Assembler::NonZero, calleeTokenReg, 305 Imm32(CalleeTokenScriptBit), ¬Function); 306 307 // Compute the number of arguments that will be pushed (excluding this and 308 // newTarget). 309 Register actualArgs = scratch; 310 masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), calleeTokenReg, actualArgs); 311 masm.loadFunctionArgCount(actualArgs, actualArgs); 312 masm.max32(actualArgs, argcReg, actualArgs); 313 314 // Align the stack. 315 if (JitStackValueAlignment == 1) { 316 masm.andToStackPtr(Imm32(~(JitStackAlignment - 1))); 317 } else { 318 MOZ_ASSERT(JitStackValueAlignment == 2); 319 // We will push actualArgs arguments, `this`, and maybe `newTarget`. 320 // Each value we push is 8 bytes. If we push an even number of values, 321 // we want to align to 16 bytes. If we push an odd number, we want to be 322 // offset from that by 8. For alignment purposes, we only care about the 323 // parity of the total, so we can use the low bit of 324 // 1 (this) + actualArgs + calleeTokenReg (for the constructing bit) 325 static_assert(CalleeToken_FunctionConstructing == 1); 326 masm.computeEffectiveAddress( 327 BaseIndex(calleeTokenReg, actualArgs, Scale::TimesOne, 1), scratch2); 328 masm.and32(Imm32(1), scratch2); 329 masm.lshift32(Imm32(3), scratch2); 330 masm.moveStackPtrTo(scratch3); 331 masm.subPtr(scratch2, scratch3); 332 masm.andPtr(Imm32(JitStackAlignment - 1), scratch3); 333 masm.subFromStackPtr(scratch3); 334 } 335 336 // We set up argCursor to point 8 bytes *after* the next argument to push. 337 // This allows us to compare against argvReg in the arguments loop while 338 // still pushing `this`. 339 Register argCursor = scratch3; 340 masm.computeEffectiveAddress(BaseValueIndex(argvReg, argcReg), argCursor); 341 342 // Push newTarget if necessary. 343 Label notConstructing; 344 masm.branchTest32(Assembler::Zero, calleeTokenReg, 345 Imm32(CalleeToken_FunctionConstructing), ¬Constructing); 346 masm.pushValue(Address(argCursor, 0)); 347 masm.bind(¬Constructing); 348 349 // Push undefined arguments if necessary, decrementing actualArgs 350 // until it matches argc. 351 Label undefLoop, doneUndef; 352 masm.bind(&undefLoop); 353 masm.branch32(Assembler::Equal, actualArgs, argcReg, &doneUndef); 354 masm.pushValue(UndefinedValue()); 355 masm.sub32(Imm32(1), actualArgs); 356 masm.jump(&undefLoop); 357 masm.bind(&doneUndef); 358 359 // Loop pushing arguments. 360 Label argLoop; 361 masm.bind(&argLoop); 362 masm.pushValue(Address(argCursor, -int32_t(sizeof(Value)))); 363 masm.subPtr(Imm32(sizeof(Value)), argCursor); 364 masm.branchPtr(Assembler::AboveOrEqual, argCursor, argvReg, &argLoop); 365 366 masm.jump(&doneArgs); 367 368 // If we're invoking a script, we will push no arguments. We can therefore 369 // simply align to the JitStackAlignment. 370 masm.bind(¬Function); 371 masm.andToStackPtr(Imm32(~(JitStackAlignment - 1))); 372 masm.bind(&doneArgs); 373 374 // Push the callee token. 375 masm.push(calleeTokenReg); 376 } 377 #endif // !JS_CODEGEN_ARM64