tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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), &notFunction);
    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), &notConstructing);
    346  masm.pushValue(Address(argCursor, 0));
    347  masm.bind(&notConstructing);
    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(&notFunction);
    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