/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "jit/JitFrames.h" #include "jit/JitRuntime.h" #include "jit/MacroAssembler.h" #include "vm/JitActivation.h" #include "vm/JSContext.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; void JitRuntime::generateExceptionTailStub(MacroAssembler& masm, Label* profilerExitTail, Label* bailoutTail) { AutoCreatedBy acb(masm, "JitRuntime::generateExceptionTailStub"); exceptionTailOffset_ = startTrampolineCode(masm); uint32_t returnValueCheckOffset = 0; masm.bind(masm.failureLabel()); masm.handleFailureWithHandlerTail(profilerExitTail, bailoutTail, &returnValueCheckOffset); exceptionTailReturnValueCheckOffset_ = returnValueCheckOffset; } void JitRuntime::generateProfilerExitFrameTailStub(MacroAssembler& masm, Label* profilerExitTail) { AutoCreatedBy acb(masm, "JitRuntime::generateProfilerExitFrameTailStub"); profilerExitFrameTailOffset_ = startTrampolineCode(masm); masm.bind(profilerExitTail); static constexpr size_t CallerFPOffset = CommonFrameLayout::offsetOfCallerFramePtr(); // Assert the caller frame's type is one of the types we expect. auto emitAssertPrevFrameType = [&masm]( Register framePtr, Register scratch, std::initializer_list types) { #ifdef DEBUG masm.loadPtr(Address(framePtr, CommonFrameLayout::offsetOfDescriptor()), scratch); masm.and32(Imm32(FrameDescriptor::TypeMask), scratch); Label checkOk; for (FrameType type : types) { masm.branch32(Assembler::Equal, scratch, Imm32(type), &checkOk); } masm.assumeUnreachable("Unexpected previous frame"); masm.bind(&checkOk); #else (void)masm; #endif }; AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(JSReturnOperand); Register scratch = regs.takeAny(); // The code generated below expects that the current frame pointer points // to an Ion or Baseline frame, at the state it would be immediately before // the frame epilogue and ret(). Thus, after this stub's business is done, it // restores the frame pointer and stack pointer, then executes a ret() and // returns directly to the caller frame, on behalf of the callee script that // jumped to this code. // // Thus the expected state is: // // [JitFrameLayout] <-- FramePointer // [frame contents] <-- StackPointer // // The generated jitcode is responsible for overwriting the // jitActivation->lastProfilingFrame field with a pointer to the previous // Ion or Baseline jit-frame that was pushed before this one. It is also // responsible for overwriting jitActivation->lastProfilingCallSite with // the return address into that frame. // // So this jitcode is responsible for "walking up" the jit stack, finding // the previous Ion or Baseline JS frame, and storing its address and the // return address into the appropriate fields on the current jitActivation. // // There are a fixed number of different path types that can lead to the // current frame, which is either a Baseline or Ion frame: // // // ^ // | // ^--- Ion (or Baseline JSOp::Resume) // | // ^--- Baseline Stub <---- Baseline // | // ^--- IonICCall <---- Ion // | // ^--- Entry Frame (BaselineInterpreter) (unwrapped) // | // ^--- Trampoline Native (unwrapped) // | // ^--- Entry Frame (CppToJSJit or WasmToJSJit) // // NOTE: Keep this in sync with JSJitProfilingFrameIterator::moveToNextFrame! Register actReg = regs.takeAny(); masm.loadJSContext(actReg); masm.loadPtr(Address(actReg, offsetof(JSContext, profilingActivation_)), actReg); Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); #ifdef DEBUG // Ensure that frame we are exiting is current lastProfilingFrame { masm.loadPtr(lastProfilingFrame, scratch); Label checkOk; masm.branchPtr(Assembler::Equal, scratch, ImmWord(0), &checkOk); masm.branchPtr(Assembler::Equal, FramePointer, scratch, &checkOk); masm.assumeUnreachable( "Mismatch between stored lastProfilingFrame and current frame " "pointer."); masm.bind(&checkOk); } #endif // Move FP into a scratch register and use that scratch register below, to // allow unwrapping frames without clobbering FP. Register fpScratch = regs.takeAny(); masm.mov(FramePointer, fpScratch); Label again; masm.bind(&again); // Load the frame descriptor into |scratch|, figure out what to do depending // on its type. masm.loadPtr(Address(fpScratch, JitFrameLayout::offsetOfDescriptor()), scratch); masm.and32(Imm32(FrameDescriptor::TypeMask), scratch); // Handling of each case is dependent on FrameDescriptor.type Label handle_BaselineOrIonJS; Label handle_BaselineStub; Label handle_TrampolineNative; Label handle_BaselineInterpreterEntry; Label handle_IonICCall; Label handle_Entry; // We check for IonJS and BaselineStub first because these are the most common // types. Calls from Baseline are usually from a BaselineStub frame. masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::IonJS), &handle_BaselineOrIonJS); masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::BaselineStub), &handle_BaselineStub); if (JitOptions.emitInterpreterEntryTrampoline) { masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::BaselineInterpreterEntry), &handle_BaselineInterpreterEntry); } masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::CppToJSJit), &handle_Entry); masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::BaselineJS), &handle_BaselineOrIonJS); masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::IonICCall), &handle_IonICCall); masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::TrampolineNative), &handle_TrampolineNative); masm.branch32(Assembler::Equal, scratch, Imm32(FrameType::WasmToJSJit), &handle_Entry); masm.assumeUnreachable( "Invalid caller frame type when returning from a JIT frame."); masm.bind(&handle_BaselineOrIonJS); { // Returning directly to a Baseline or Ion frame. // lastProfilingCallSite := ReturnAddress masm.loadPtr(Address(fpScratch, JitFrameLayout::offsetOfReturnAddress()), scratch); masm.storePtr(scratch, lastProfilingCallSite); // lastProfilingFrame := CallerFrame masm.loadPtr(Address(fpScratch, CallerFPOffset), scratch); masm.storePtr(scratch, lastProfilingFrame); masm.moveToStackPtr(FramePointer); masm.pop(FramePointer); masm.ret(); } // Shared implementation for BaselineStub and IonICCall frames. auto emitHandleStubFrame = [&](FrameType expectedPrevType) { // Load pointer to stub frame and assert type of its caller frame. masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); emitAssertPrevFrameType(fpScratch, scratch, {expectedPrevType}); // lastProfilingCallSite := StubFrame.ReturnAddress masm.loadPtr(Address(fpScratch, CommonFrameLayout::offsetOfReturnAddress()), scratch); masm.storePtr(scratch, lastProfilingCallSite); // lastProfilingFrame := StubFrame.CallerFrame masm.loadPtr(Address(fpScratch, CallerFPOffset), scratch); masm.storePtr(scratch, lastProfilingFrame); masm.moveToStackPtr(FramePointer); masm.pop(FramePointer); masm.ret(); }; masm.bind(&handle_BaselineStub); { // BaselineJS => BaselineStub frame. emitHandleStubFrame(FrameType::BaselineJS); } masm.bind(&handle_IonICCall); { // IonJS => IonICCall frame. emitHandleStubFrame(FrameType::IonJS); } masm.bind(&handle_TrampolineNative); { // There can be multiple previous frame types so just "unwrap" this frame // and try again. masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); emitAssertPrevFrameType(fpScratch, scratch, {FrameType::IonJS, FrameType::BaselineStub, FrameType::CppToJSJit, FrameType::WasmToJSJit}); masm.jump(&again); } if (JitOptions.emitInterpreterEntryTrampoline) { masm.bind(&handle_BaselineInterpreterEntry); { // Unwrap the baseline interpreter entry frame and try again. masm.loadPtr(Address(fpScratch, CallerFPOffset), fpScratch); emitAssertPrevFrameType(fpScratch, scratch, {FrameType::IonJS, FrameType::BaselineJS, FrameType::BaselineStub, FrameType::CppToJSJit, FrameType::WasmToJSJit, FrameType::IonICCall}); masm.jump(&again); } } masm.bind(&handle_Entry); { // FrameType::CppToJSJit / FrameType::WasmToJSJit // // A fast-path wasm->jit transition frame is an entry frame from the point // of view of the JIT. // Store null into both fields. masm.movePtr(ImmPtr(nullptr), scratch); masm.storePtr(scratch, lastProfilingCallSite); masm.storePtr(scratch, lastProfilingFrame); masm.moveToStackPtr(FramePointer); masm.pop(FramePointer); masm.ret(); } } #ifndef JS_CODEGEN_ARM64 // This is a shared path used by generateEnterJit on all architectures // except arm64, which has its own implementation that avoids using the // pseudo stack pointer. void JitRuntime::generateEnterJitShared(MacroAssembler& masm, Register argcReg, Register argvReg, Register calleeTokenReg, Register scratch, Register scratch2, Register scratch3) { // Preconditions: // - argcReg contains the number of actual args passed (not including this). // - argvReg points to the beginning of an array of argcReg argument values, // *not including* `this`. `this` is at argvReg[-1]. If newTarget exists, // it follows the last argument at argvReg[argcReg]. // - calleeTokenReg contains the calleeToken, still tagged. // - no alignment is assumed. // // Postconditions: // - stack padding has been inserted if necessary for alignment. // - if necessary, newTarget has been copied into place. // - if calleeToken is a function and argc < fun->nargs(), `undefined` values // have been pushed to make up the difference. // - the arguments have been pushed to the stack. // - the callee token has been pushed // - the descriptor has *not* been pushed, because x86 doesn't have enough // registers available to pass in actualArgs. static_assert( sizeof(JitFrameLayout) % JitStackAlignment == 0, "No need to consider the JitFrameLayout for aligning the stack"); Label notFunction, doneArgs; masm.branchTest32(Assembler::NonZero, calleeTokenReg, Imm32(CalleeTokenScriptBit), ¬Function); // Compute the number of arguments that will be pushed (excluding this and // newTarget). Register actualArgs = scratch; masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), calleeTokenReg, actualArgs); masm.loadFunctionArgCount(actualArgs, actualArgs); masm.max32(actualArgs, argcReg, actualArgs); // Align the stack. if (JitStackValueAlignment == 1) { masm.andToStackPtr(Imm32(~(JitStackAlignment - 1))); } else { MOZ_ASSERT(JitStackValueAlignment == 2); // We will push actualArgs arguments, `this`, and maybe `newTarget`. // Each value we push is 8 bytes. If we push an even number of values, // we want to align to 16 bytes. If we push an odd number, we want to be // offset from that by 8. For alignment purposes, we only care about the // parity of the total, so we can use the low bit of // 1 (this) + actualArgs + calleeTokenReg (for the constructing bit) static_assert(CalleeToken_FunctionConstructing == 1); masm.computeEffectiveAddress( BaseIndex(calleeTokenReg, actualArgs, Scale::TimesOne, 1), scratch2); masm.and32(Imm32(1), scratch2); masm.lshift32(Imm32(3), scratch2); masm.moveStackPtrTo(scratch3); masm.subPtr(scratch2, scratch3); masm.andPtr(Imm32(JitStackAlignment - 1), scratch3); masm.subFromStackPtr(scratch3); } // We set up argCursor to point 8 bytes *after* the next argument to push. // This allows us to compare against argvReg in the arguments loop while // still pushing `this`. Register argCursor = scratch3; masm.computeEffectiveAddress(BaseValueIndex(argvReg, argcReg), argCursor); // Push newTarget if necessary. Label notConstructing; masm.branchTest32(Assembler::Zero, calleeTokenReg, Imm32(CalleeToken_FunctionConstructing), ¬Constructing); masm.pushValue(Address(argCursor, 0)); masm.bind(¬Constructing); // Push undefined arguments if necessary, decrementing actualArgs // until it matches argc. Label undefLoop, doneUndef; masm.bind(&undefLoop); masm.branch32(Assembler::Equal, actualArgs, argcReg, &doneUndef); masm.pushValue(UndefinedValue()); masm.sub32(Imm32(1), actualArgs); masm.jump(&undefLoop); masm.bind(&doneUndef); // Loop pushing arguments. Label argLoop; masm.bind(&argLoop); masm.pushValue(Address(argCursor, -int32_t(sizeof(Value)))); masm.subPtr(Imm32(sizeof(Value)), argCursor); masm.branchPtr(Assembler::AboveOrEqual, argCursor, argvReg, &argLoop); masm.jump(&doneArgs); // If we're invoking a script, we will push no arguments. We can therefore // simply align to the JitStackAlignment. masm.bind(¬Function); masm.andToStackPtr(Imm32(~(JitStackAlignment - 1))); masm.bind(&doneArgs); // Push the callee token. masm.push(calleeTokenReg); } #endif // !JS_CODEGEN_ARM64