tor-browser

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

WasmBCFrame.cpp (19008B)


      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 2016 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 #include "wasm/WasmBCFrame.h"
     20 
     21 #include "wasm/WasmBaselineCompile.h"  // For BaseLocalIter
     22 #include "wasm/WasmBCClass.h"
     23 
     24 #include "jit/MacroAssembler-inl.h"
     25 #include "wasm/WasmBCClass-inl.h"
     26 #include "wasm/WasmBCCodegen-inl.h"
     27 #include "wasm/WasmBCRegDefs-inl.h"
     28 #include "wasm/WasmBCRegMgmt-inl.h"
     29 #include "wasm/WasmBCStkMgmt-inl.h"
     30 
     31 namespace js {
     32 namespace wasm {
     33 
     34 using mozilla::Maybe;
     35 using mozilla::Some;
     36 
     37 //////////////////////////////////////////////////////////////////////////////
     38 //
     39 // BaseLocalIter methods.
     40 
     41 BaseLocalIter::BaseLocalIter(const ValTypeVector& locals,
     42                             const ArgTypeVector& args, bool debugEnabled)
     43    : locals_(locals),
     44      args_(args),
     45      argsIter_(args_, ABIKind::Wasm),
     46      index_(0),
     47      frameSize_(0),
     48      nextFrameSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0),
     49      frameOffset_(INT32_MAX),
     50      stackResultPointerOffset_(INT32_MAX),
     51      mirType_(MIRType::Undefined),
     52      done_(false) {
     53  MOZ_ASSERT(args.lengthWithoutStackResults() <= locals.length());
     54  settle();
     55 }
     56 
     57 int32_t BaseLocalIter::pushLocal(size_t nbytes) {
     58  MOZ_ASSERT(nbytes % 4 == 0 && nbytes <= 16);
     59  nextFrameSize_ = AlignBytes(frameSize_, nbytes) + nbytes;
     60  return nextFrameSize_;  // Locals grow down so capture base address.
     61 }
     62 
     63 void BaseLocalIter::settle() {
     64  MOZ_ASSERT(!done_);
     65  frameSize_ = nextFrameSize_;
     66 
     67  if (!argsIter_.done()) {
     68    mirType_ = argsIter_.mirType();
     69    MIRType concreteType = mirType_;
     70    switch (mirType_) {
     71      case MIRType::StackResults:
     72        // The pointer to stack results is handled like any other argument:
     73        // either addressed in place if it is passed on the stack, or we spill
     74        // it in the frame if it's in a register.
     75        MOZ_ASSERT(args_.isSyntheticStackResultPointerArg(index_));
     76        concreteType = MIRType::Pointer;
     77        [[fallthrough]];
     78      case MIRType::Int32:
     79      case MIRType::Int64:
     80      case MIRType::Double:
     81      case MIRType::Float32:
     82      case MIRType::WasmAnyRef:
     83 #ifdef ENABLE_WASM_SIMD
     84      case MIRType::Simd128:
     85 #endif
     86        if (argsIter_->argInRegister()) {
     87          frameOffset_ = pushLocal(MIRTypeToSize(concreteType));
     88        } else {
     89          frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
     90        }
     91        break;
     92      default:
     93        MOZ_CRASH("Argument type");
     94    }
     95    if (mirType_ == MIRType::StackResults) {
     96      stackResultPointerOffset_ = frameOffset();
     97      // Advance past the synthetic stack result pointer argument and fall
     98      // through to the next case.
     99      argsIter_++;
    100      frameSize_ = nextFrameSize_;
    101      MOZ_ASSERT(argsIter_.done());
    102    } else {
    103      return;
    104    }
    105  }
    106 
    107  if (index_ < locals_.length()) {
    108    switch (locals_[index_].kind()) {
    109      case ValType::I32:
    110      case ValType::I64:
    111      case ValType::F32:
    112      case ValType::F64:
    113 #ifdef ENABLE_WASM_SIMD
    114      case ValType::V128:
    115 #endif
    116      case ValType::Ref:
    117        mirType_ = locals_[index_].toMIRType();
    118        frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
    119        break;
    120      default:
    121        MOZ_CRASH("Compiler bug: Unexpected local type");
    122    }
    123    return;
    124  }
    125 
    126  done_ = true;
    127 }
    128 
    129 void BaseLocalIter::operator++(int) {
    130  MOZ_ASSERT(!done_);
    131  index_++;
    132  if (!argsIter_.done()) {
    133    argsIter_++;
    134  }
    135  settle();
    136 }
    137 
    138 //////////////////////////////////////////////////////////////////////////////
    139 //
    140 // Stack map methods.
    141 
    142 bool BaseCompiler::createStackMap(const char* who) {
    143  const ExitStubMapVector noExtras;
    144  StackMap* stackMap;
    145  return stackMapGenerator_.createStackMap(
    146             who, noExtras, HasDebugFrameWithLiveRefs::No, stk_, &stackMap) &&
    147         (!stackMap || stackMaps_->add(masm.currentOffset(), stackMap));
    148 }
    149 
    150 bool BaseCompiler::createStackMap(const char* who, CodeOffset assemblerOffset) {
    151  const ExitStubMapVector noExtras;
    152  StackMap* stackMap;
    153  return stackMapGenerator_.createStackMap(
    154             who, noExtras, HasDebugFrameWithLiveRefs::No, stk_, &stackMap) &&
    155         (!stackMap || stackMaps_->add(assemblerOffset.offset(), stackMap));
    156 }
    157 
    158 bool BaseCompiler::createStackMap(
    159    const char* who, HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) {
    160  const ExitStubMapVector noExtras;
    161  StackMap* stackMap;
    162  return stackMapGenerator_.createStackMap(
    163             who, noExtras, debugFrameWithLiveRefs, stk_, &stackMap) &&
    164         (!stackMap || stackMaps_->add(masm.currentOffset(), stackMap));
    165 }
    166 
    167 bool MachineStackTracker::cloneTo(MachineStackTracker* dst) {
    168  MOZ_ASSERT(dst->vec_.empty());
    169  if (!dst->vec_.appendAll(vec_)) {
    170    return false;
    171  }
    172  dst->numPtrs_ = numPtrs_;
    173  return true;
    174 }
    175 
    176 bool StackMapGenerator::generateStackmapEntriesForTrapExit(
    177    const ArgTypeVector& args, ExitStubMapVector* extras) {
    178  return GenerateStackmapEntriesForTrapExit(args, trapExitLayout_,
    179                                            trapExitLayoutNumWords_, extras);
    180 }
    181 
    182 bool StackMapGenerator::createStackMap(
    183    const char* who, const ExitStubMapVector& extras,
    184    HasDebugFrameWithLiveRefs debugFrameWithLiveRefs, const StkVector& stk,
    185    wasm::StackMap** result) {
    186  // Always initialize the result value
    187  *result = nullptr;
    188 
    189  size_t countedPointers = machineStackTracker.numPtrs() + memRefsOnStk;
    190 #ifndef DEBUG
    191  // An important optimization.  If there are obviously no pointers, as
    192  // we expect in the majority of cases, exit quickly.
    193  if (countedPointers == 0 &&
    194      debugFrameWithLiveRefs == HasDebugFrameWithLiveRefs::No) {
    195    // We can skip creating the map if there are no |true| elements in
    196    // |extras|.
    197    bool extrasHasRef = false;
    198    for (bool b : extras) {
    199      if (b) {
    200        extrasHasRef = true;
    201        break;
    202      }
    203    }
    204    if (!extrasHasRef) {
    205      return true;
    206    }
    207  }
    208 #else
    209  // In the debug case, create the stackmap regardless, and cross-check
    210  // the pointer-counting below.  We expect the final map to have
    211  // |countedPointers| in total.  This doesn't include those in the
    212  // DebugFrame, but they do not appear in the map's bitmap.  Note that
    213  // |countedPointers| is debug-only from this point onwards.
    214  for (bool b : extras) {
    215    countedPointers += (b ? 1 : 0);
    216  }
    217 #endif
    218 
    219  // Start with the frame-setup map, and add operand-stack information to
    220  // that.  augmentedMst holds live data only within individual calls to
    221  // createStackMap.
    222  augmentedMst.clear();
    223  if (!machineStackTracker.cloneTo(&augmentedMst)) {
    224    return false;
    225  }
    226 
    227  // At this point, augmentedMst only contains entries covering the
    228  // incoming argument area (if any) and for the area allocated by this
    229  // function's prologue.  We now need to calculate how far the machine's
    230  // stack pointer is below where it was at the start of the body.  But we
    231  // must take care not to include any words pushed as arguments to an
    232  // upcoming function call, since those words belong to the stackmap of
    233  // the callee, not to the stackmap of this function.  Any alignment padding
    234  // for the args also belongs to the callee.
    235  //
    236  // The only padding belonging to the stackmap of this function is that
    237  // required to align the upcoming frame. This is accounted for where
    238  // framePushedExcludingOutboundCallArgs is set, in startCallArgs(), and is
    239  // comprised of just one component:
    240  //
    241  // * call->frameAlignAdjustment
    242  Maybe<uint32_t> framePushedExcludingArgs;
    243  if (framePushedAtEntryToBody.isNothing()) {
    244    // Still in the prologue.  framePushedExcludingArgs remains Nothing.
    245    MOZ_ASSERT(framePushedExcludingOutboundCallArgs.isNothing());
    246  } else {
    247    // In the body.
    248    MOZ_ASSERT(masm_.framePushed() >= framePushedAtEntryToBody.value());
    249    if (framePushedExcludingOutboundCallArgs.isSome()) {
    250      // In the body, and we've potentially pushed some args onto the stack.
    251      // We must ignore them when sizing the stackmap.
    252      MOZ_ASSERT(masm_.framePushed() >=
    253                 framePushedExcludingOutboundCallArgs.value());
    254      MOZ_ASSERT(framePushedExcludingOutboundCallArgs.value() >=
    255                 framePushedAtEntryToBody.value());
    256      framePushedExcludingArgs =
    257          Some(framePushedExcludingOutboundCallArgs.value());
    258    } else {
    259      // In the body, but not with call args on the stack.  The stackmap
    260      // must be sized so as to extend all the way "down" to
    261      // masm_.framePushed().
    262      framePushedExcludingArgs = Some(masm_.framePushed());
    263    }
    264  }
    265 
    266  if (framePushedExcludingArgs.isSome()) {
    267    uint32_t bodyPushedBytes =
    268        framePushedExcludingArgs.value() - framePushedAtEntryToBody.value();
    269    MOZ_ASSERT(0 == bodyPushedBytes % sizeof(void*));
    270    if (!augmentedMst.pushNonGCPointers(bodyPushedBytes / sizeof(void*))) {
    271      return false;
    272    }
    273  }
    274 
    275  // Scan the operand stack, marking pointers in the just-added new
    276  // section.
    277  MOZ_ASSERT_IF(framePushedAtEntryToBody.isNothing(), stk.empty());
    278  MOZ_ASSERT_IF(framePushedExcludingArgs.isNothing(), stk.empty());
    279 
    280  for (const Stk& v : stk) {
    281 #ifndef DEBUG
    282    // We don't track roots in registers, per rationale below, so if this
    283    // doesn't hold, something is seriously wrong, and we're likely to get a
    284    // GC-related crash.
    285    MOZ_RELEASE_ASSERT(v.kind() != Stk::RegisterRef);
    286    if (v.kind() != Stk::MemRef) {
    287      continue;
    288    }
    289 #else
    290    // Take the opportunity to check everything we reasonably can about
    291    // operand stack elements.
    292    switch (v.kind()) {
    293      case Stk::MemI32:
    294      case Stk::MemI64:
    295      case Stk::MemF32:
    296      case Stk::MemF64:
    297      case Stk::ConstI32:
    298      case Stk::ConstI64:
    299      case Stk::ConstF32:
    300      case Stk::ConstF64:
    301 #  ifdef ENABLE_WASM_SIMD
    302      case Stk::MemV128:
    303      case Stk::ConstV128:
    304 #  endif
    305        // All of these have uninteresting type.
    306        continue;
    307      case Stk::LocalI32:
    308      case Stk::LocalI64:
    309      case Stk::LocalF32:
    310      case Stk::LocalF64:
    311 #  ifdef ENABLE_WASM_SIMD
    312      case Stk::LocalV128:
    313 #  endif
    314        // These also have uninteresting type.  Check that they live in the
    315        // section of stack set up by beginFunction().  The unguarded use of
    316        // |value()| here is safe due to the assertion above this loop.
    317        MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
    318        continue;
    319      case Stk::RegisterI32:
    320      case Stk::RegisterI64:
    321      case Stk::RegisterF32:
    322      case Stk::RegisterF64:
    323 #  ifdef ENABLE_WASM_SIMD
    324      case Stk::RegisterV128:
    325 #  endif
    326        // These also have uninteresting type, but more to the point: all
    327        // registers holding live values should have been flushed to the
    328        // machine stack immediately prior to the instruction to which this
    329        // stackmap pertains.  So these can't happen.
    330        MOZ_CRASH("createStackMap: operand stack has Register-non-Ref");
    331      case Stk::MemRef:
    332        // This is the only case we care about.  We'll handle it after the
    333        // switch.
    334        break;
    335      case Stk::LocalRef:
    336        // We need the stackmap to mention this pointer, but it should
    337        // already be in the machineStackTracker section created by
    338        // beginFunction().
    339        MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
    340        continue;
    341      case Stk::ConstRef:
    342        // This can currently only be a null pointer.
    343        MOZ_ASSERT(v.refval() == 0);
    344        continue;
    345      case Stk::RegisterRef:
    346        // This can't happen, per rationale above.
    347        MOZ_CRASH("createStackMap: operand stack contains RegisterRef");
    348      default:
    349        MOZ_CRASH("createStackMap: unknown operand stack element");
    350    }
    351 #endif
    352    // v.offs() holds masm.framePushed() at the point immediately after it
    353    // was pushed on the stack.  Since it's still on the stack,
    354    // masm.framePushed() can't be less.
    355    MOZ_ASSERT(v.offs() <= framePushedExcludingArgs.value());
    356    uint32_t offsFromMapLowest = framePushedExcludingArgs.value() - v.offs();
    357    MOZ_ASSERT(0 == offsFromMapLowest % sizeof(void*));
    358    augmentedMst.setGCPointer(offsFromMapLowest / sizeof(void*));
    359  }
    360 
    361  MOZ_ASSERT(numStackArgBytes % sizeof(void*) == 0);
    362  const size_t numStackArgWords = numStackArgBytes / sizeof(void*);
    363  const size_t numStackArgPaddingBytes =
    364      AlignStackArgAreaSize(numStackArgBytes) - numStackArgBytes;
    365  const size_t numStackArgPaddingWords =
    366      numStackArgPaddingBytes / sizeof(void*);
    367 
    368  // Create the final StackMap.  The initial map is zeroed out, so there's
    369  // no need to write zero bits in it.
    370  const uint32_t extraWords = extras.length();
    371  const uint32_t augmentedMstWords = augmentedMst.length();
    372  const uint32_t numMappedWords =
    373      numStackArgPaddingWords + extraWords + augmentedMstWords;
    374  StackMap* stackMap = stackMaps_->create(numMappedWords);
    375  if (!stackMap) {
    376    return false;
    377  }
    378 
    379  {
    380    // First the exit stub extra words, if any.
    381    uint32_t i = 0;
    382    for (bool b : extras) {
    383      if (b) {
    384        stackMap->set(i, StackMap::Kind::AnyRef);
    385      }
    386      i++;
    387    }
    388  }
    389  {
    390    // Followed by the "main" part of the map.
    391    //
    392    // This is really just a bit-array copy, so it is reasonable to ask
    393    // whether the representation of MachineStackTracker could be made more
    394    // similar to that of StackMap, so that the copy could be done with
    395    // `memcpy`.  Unfortunately it's not so simple; see comment on `class
    396    // MachineStackTracker` for details.
    397    MachineStackTracker::Iter iter(augmentedMst);
    398    while (true) {
    399      size_t i = iter.get();
    400      if (i == MachineStackTracker::Iter::FINISHED) {
    401        break;
    402      }
    403      stackMap->set(extraWords + i, StackMap::Kind::AnyRef);
    404    }
    405  }
    406 
    407  stackMap->setExitStubWords(extraWords);
    408 
    409  // Record in the map, how far down from the highest address the Frame* is.
    410  // Take the opportunity to check that we haven't marked any part of the
    411  // Frame itself as a pointer.
    412  stackMap->setFrameOffsetFromTop(numStackArgPaddingWords + numStackArgWords +
    413                                  sizeof(Frame) / sizeof(void*));
    414 #ifdef DEBUG
    415  for (uint32_t i = 0; i < sizeof(Frame) / sizeof(void*); i++) {
    416    MOZ_ASSERT(stackMap->get(stackMap->header.numMappedWords -
    417                             stackMap->header.frameOffsetFromTop + i) ==
    418               StackMap::Kind::POD);
    419  }
    420 #endif
    421 
    422  // Note the presence of a DebugFrame with live pointers, if any.
    423  if (debugFrameWithLiveRefs != HasDebugFrameWithLiveRefs::No) {
    424    stackMap->setHasDebugFrameWithLiveRefs();
    425  }
    426 
    427 #ifdef DEBUG
    428  {
    429    // Crosscheck the map pointer counting.
    430    uint32_t nw = stackMap->header.numMappedWords;
    431    uint32_t np = 0;
    432    for (uint32_t i = 0; i < nw; i++) {
    433      if (stackMap->get(i) == StackMap::Kind::AnyRef) {
    434        np += 1;
    435      }
    436    }
    437    MOZ_ASSERT(size_t(np) == countedPointers);
    438  }
    439 #endif
    440 
    441  *result = stackMaps_->finalize(stackMap);
    442  return true;
    443 }
    444 
    445 //////////////////////////////////////////////////////////////////////////////
    446 //
    447 // Stack frame methods.
    448 
    449 void BaseStackFrame::zeroLocals(BaseRegAlloc* ra) {
    450  MOZ_ASSERT(varLow_ != UINT32_MAX);
    451 
    452  if (varLow_ == varHigh_) {
    453    return;
    454  }
    455 
    456  static const uint32_t wordSize = sizeof(void*);
    457 
    458  // The adjustments to 'low' by the size of the item being stored compensates
    459  // for the fact that locals offsets are the offsets from Frame to the bytes
    460  // directly "above" the locals in the locals area.  See comment at Local.
    461 
    462  // On 64-bit systems we may have 32-bit alignment for the local area as it
    463  // may be preceded by parameters and prologue/debug data.
    464 
    465  uint32_t low = varLow_;
    466  if (low % wordSize) {
    467    masm.store32(Imm32(0), Address(sp_, localOffset(low + 4)));
    468    low += 4;
    469  }
    470  MOZ_ASSERT(low % wordSize == 0);
    471 
    472  const uint32_t high = AlignBytes(varHigh_, wordSize);
    473 
    474  // An UNROLL_LIMIT of 16 is chosen so that we only need an 8-bit signed
    475  // immediate to represent the offset in the store instructions in the loop
    476  // on x64.
    477 
    478  const uint32_t UNROLL_LIMIT = 16;
    479  const uint32_t initWords = (high - low) / wordSize;
    480  const uint32_t tailWords = initWords % UNROLL_LIMIT;
    481  const uint32_t loopHigh = high - (tailWords * wordSize);
    482 
    483  // With only one word to initialize, just store an immediate zero.
    484 
    485  if (initWords == 1) {
    486    masm.storePtr(ImmWord(0), Address(sp_, localOffset(low + wordSize)));
    487    return;
    488  }
    489 
    490  // For other cases, it's best to have a zero in a register.
    491  //
    492  // One can do more here with SIMD registers (store 16 bytes at a time) or
    493  // with instructions like STRD on ARM (store 8 bytes at a time), but that's
    494  // for another day.
    495 
    496  RegI32 zero = ra->needI32();
    497  masm.mov(ImmWord(0), zero);
    498 
    499  // For the general case we want to have a loop body of UNROLL_LIMIT stores
    500  // and then a tail of less than UNROLL_LIMIT stores.  When initWords is less
    501  // than 2*UNROLL_LIMIT the loop trip count is at most 1 and there is no
    502  // benefit to having the pointer calculations and the compare-and-branch.
    503  // So we completely unroll when we have initWords < 2 * UNROLL_LIMIT.  (In
    504  // this case we'll end up using 32-bit offsets on x64 for up to half of the
    505  // stores, though.)
    506 
    507  // Fully-unrolled case.
    508 
    509  if (initWords < 2 * UNROLL_LIMIT) {
    510    for (uint32_t i = low; i < high; i += wordSize) {
    511      masm.storePtr(zero, Address(sp_, localOffset(i + wordSize)));
    512    }
    513    ra->freeI32(zero);
    514    return;
    515  }
    516 
    517  // Unrolled loop with a tail. Stores will use negative offsets. That's OK
    518  // for x86 and ARM, at least.
    519 
    520  // Compute pointer to the highest-addressed slot on the frame.
    521  RegI32 p = ra->needI32();
    522  masm.computeEffectiveAddress(Address(sp_, localOffset(low + wordSize)), p);
    523 
    524  // Compute pointer to the lowest-addressed slot on the frame that will be
    525  // initialized by the loop body.
    526  RegI32 lim = ra->needI32();
    527  masm.computeEffectiveAddress(Address(sp_, localOffset(loopHigh + wordSize)),
    528                               lim);
    529 
    530  // The loop body.  Eventually we'll have p == lim and exit the loop.
    531  Label again;
    532  masm.bind(&again);
    533  for (uint32_t i = 0; i < UNROLL_LIMIT; ++i) {
    534    masm.storePtr(zero, Address(p, -(wordSize * i)));
    535  }
    536  masm.subPtr(Imm32(UNROLL_LIMIT * wordSize), p);
    537  masm.branchPtr(Assembler::LessThan, lim, p, &again);
    538 
    539  // The tail.
    540  for (uint32_t i = 0; i < tailWords; ++i) {
    541    masm.storePtr(zero, Address(p, -(wordSize * i)));
    542  }
    543 
    544  ra->freeI32(p);
    545  ra->freeI32(lim);
    546  ra->freeI32(zero);
    547 }
    548 
    549 }  // namespace wasm
    550 }  // namespace js