tor-browser

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

SimpleAllocator.h (14285B)


      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 #ifndef jit_SimpleAllocator_h
      8 #define jit_SimpleAllocator_h
      9 
     10 #include "mozilla/Array.h"
     11 #include "mozilla/Attributes.h"
     12 #include "mozilla/Maybe.h"
     13 
     14 #include "jit/RegisterAllocator.h"
     15 #include "jit/SparseBitSet.h"
     16 #include "jit/StackSlotAllocator.h"
     17 
     18 namespace js {
     19 namespace jit {
     20 
     21 class MOZ_STACK_CLASS SimpleAllocator : protected RegisterAllocator {
     22 public:
     23  using IsStackAllocated = std::true_type;
     24 
     25 private:
     26  // Information about a virtual register.
     27  class VirtualRegister {
     28    // The definition and the id of the LIR instruction that contains it.
     29    LDefinition* def_;
     30    uint32_t insId_;
     31 
     32    // The id of the LIR instruction that's considered the 'last use' of this
     33    // virtual register. After this instruction we free any register or stack
     34    // slot allocated for this virtual register and we mark this virtual
     35    // register as dead.
     36    uint32_t lastUseInsId_;
     37 
     38    // Either UINT32_MAX (meaning no stack slot has been allocated) or the stack
     39    // slot offset and width of a slot we allocated for this virtual register.
     40    static constexpr uint32_t NoStackSlotAndWidth = UINT32_MAX;
     41    uint32_t stackSlotAndWidth_ = NoStackSlotAndWidth;
     42 
     43    // If this virtual register has fixed register uses, we use this register
     44    // hint when allocating registers to help avoid moves.
     45    // This has three states:
     46    // - Nothing: we didn't see any fixed register uses.
     47    // - Some(valid AnyRegister): there are fixed register uses and they all use
     48    //   this register.
     49    // - Some(Invalid AnyRegister): there are multiple fixed register uses for
     50    //   different registers and we gave up on assigning a register hint.
     51    mozilla::Maybe<AnyRegister> fixedUseHint_;
     52 
     53    // The index in the allocator's allocatedRegs_ vector, or NoRegisterIndex if
     54    // this vreg has no allocated register.
     55    //
     56    // In some uncommon cases a vreg can have multiple registers assigned to it
     57    // and in this case the index is for one of these registers.
     58    // See hasMultipleRegsForVreg_.
     59    static constexpr size_t NoRegisterIndex = UINT8_MAX;
     60    uint8_t registerIndex_ = NoRegisterIndex;
     61 
     62    // Assert registerIndex_ fits in a byte. There must be at most one
     63    // AllocatedRegister entry for each AnyRegister value.
     64    static_assert(sizeof(AnyRegister) == sizeof(registerIndex_));
     65    static_assert(AnyRegister::Invalid == NoRegisterIndex);
     66 
     67    // Whether this virtual register is for a Temp.
     68    bool isTemp_ : 1;
     69 
     70    // Whether this virtual register has a stack location. This location is
     71    // either a stack slot we allocated (see stackSlotAndWidth_) or this virtual
     72    // register came with an assigned stack location (for example for stack
     73    // arguments).
     74    bool hasStackLocation_ : 1;
     75 
     76    // Whether this virtual register is for a GC type that needs to be added to
     77    // safepoints.
     78    bool isGCType_ : 1;
     79 
     80    // Whether this virtual register is dead. This is set after allocating
     81    // registers for the lastUseInsId_ instruction.
     82    bool isDead_ : 1;
     83 
     84   public:
     85    VirtualRegister() = default;
     86 
     87    VirtualRegister(VirtualRegister&) = delete;
     88    void operator=(VirtualRegister&) = delete;
     89 
     90    void init(LNode* ins, LDefinition* def, bool isTemp) {
     91      def_ = def;
     92      insId_ = ins->id();
     93      lastUseInsId_ = ins->id();
     94      isTemp_ = isTemp;
     95      hasStackLocation_ = false;
     96      isGCType_ = def->isSafepointGCType(ins);
     97      isDead_ = false;
     98    }
     99 
    100    LDefinition* def() const { return def_; }
    101    uint32_t insId() const { return insId_; }
    102    uint32_t lastUseInsId() const { return lastUseInsId_; }
    103 
    104    uint8_t registerIndex() const {
    105      MOZ_ASSERT(hasRegister());
    106      return registerIndex_;
    107    }
    108    bool hasRegister() const {
    109      MOZ_ASSERT(!isDead());
    110      return registerIndex_ != NoRegisterIndex;
    111    }
    112    void setRegisterIndex(uint8_t registerIndex) {
    113      MOZ_ASSERT(!isDead());
    114      MOZ_ASSERT(registerIndex < NoRegisterIndex);
    115      registerIndex_ = registerIndex;
    116    }
    117    void clearRegisterIndex() {
    118      MOZ_ASSERT(!isDead());
    119      registerIndex_ = NoRegisterIndex;
    120    }
    121 
    122    LAllocation stackLocation() const {
    123      MOZ_ASSERT(hasStackLocation());
    124      if (hasAllocatedStackSlot()) {
    125        return LStackSlot(stackSlot());
    126      }
    127      MOZ_ASSERT(def()->policy() == LDefinition::FIXED);
    128      MOZ_ASSERT(!def()->output()->isAnyRegister());
    129      return *def()->output();
    130    }
    131    void setHasStackLocation() {
    132      MOZ_ASSERT(!isDead());
    133      MOZ_ASSERT(!isTemp());
    134      MOZ_ASSERT(!hasStackLocation_);
    135      MOZ_ASSERT(stackSlotAndWidth_ == NoStackSlotAndWidth);
    136      hasStackLocation_ = true;
    137    }
    138 
    139    bool hasAllocatedStackSlot() const {
    140      MOZ_ASSERT(!isDead());
    141      MOZ_ASSERT_IF(stackSlotAndWidth_ != NoStackSlotAndWidth,
    142                    hasStackLocation_);
    143      return stackSlotAndWidth_ != NoStackSlotAndWidth;
    144    }
    145    LStackSlot::SlotAndWidth stackSlot() const {
    146      MOZ_ASSERT(hasAllocatedStackSlot());
    147      return LStackSlot::SlotAndWidth::fromData(stackSlotAndWidth_);
    148    }
    149    void setAllocatedStackSlot(LStackSlot::SlotAndWidth slot) {
    150      setHasStackLocation();
    151      MOZ_ASSERT(stackSlotAndWidth_ == NoStackSlotAndWidth);
    152      MOZ_ASSERT(slot.data() != NoStackSlotAndWidth);
    153      stackSlotAndWidth_ = slot.data();
    154    }
    155 
    156    void markDead() {
    157      MOZ_ASSERT(!isDead());
    158      isDead_ = true;
    159    }
    160 
    161    mozilla::Maybe<AnyRegister> fixedUseHint() const { return fixedUseHint_; }
    162    void setFixedUseHint(AnyRegister reg) {
    163      fixedUseHint_ = mozilla::Some(reg);
    164    }
    165 
    166    bool hasStackLocation() const { return hasStackLocation_; }
    167    bool isGCType() const { return isGCType_; }
    168    bool isTemp() const { return isTemp_; }
    169    bool isDead() const { return isDead_; }
    170 
    171    void updateLastUseId(uint32_t useInsId) {
    172      MOZ_ASSERT(!isTemp());
    173      MOZ_ASSERT(useInsId > insId_);
    174      if (useInsId > lastUseInsId_) {
    175        lastUseInsId_ = useInsId;
    176      }
    177    }
    178  };
    179 
    180  // Information about virtual registers.
    181  Vector<VirtualRegister, 0, JitAllocPolicy> vregs_;
    182 
    183  // Information about an allocated register.
    184  class AllocatedRegister {
    185    static constexpr size_t VregBits = 24;
    186    static constexpr size_t RegisterBits = 8;
    187 
    188    // The virtual register.
    189    uint32_t vregId_ : VregBits;
    190 
    191    // The AnyRegister code. We don't use AnyRegister directly because the
    192    // compiler adds additional padding bytes on Windows if we do that.
    193    uint32_t reg_ : RegisterBits;
    194 
    195    // Id of the last LIR instruction that used this allocated register. Used
    196    // for eviction heuristics.
    197    uint32_t lastUsedAtInsId_;
    198 
    199    static_assert(MAX_VIRTUAL_REGISTERS <= (1 << VregBits) - 1);
    200    static_assert(sizeof(AnyRegister::Code) * 8 == RegisterBits);
    201 
    202   public:
    203    AllocatedRegister(uint32_t vreg, AnyRegister reg, uint32_t lastUsedAtInsId)
    204        : vregId_(vreg), reg_(reg.code()), lastUsedAtInsId_(lastUsedAtInsId) {}
    205 
    206    uint32_t vregId() const { return vregId_; }
    207    AnyRegister reg() const { return AnyRegister::FromCode(reg_); }
    208    uint32_t lastUsedAtInsId() const { return lastUsedAtInsId_; }
    209    void setLastUsedAtInsId(uint32_t insId) {
    210      MOZ_ASSERT(insId >= lastUsedAtInsId_);
    211      lastUsedAtInsId_ = insId;
    212    }
    213  };
    214  static_assert(sizeof(AllocatedRegister) == 2 * sizeof(uint32_t),
    215                "AllocatedRegister should not have unnecessary padding");
    216 
    217  // Information about allocated physical registers.
    218  using AllocatedRegisterVector =
    219      Vector<AllocatedRegister, 16, BackgroundSystemAllocPolicy>;
    220  AllocatedRegisterVector allocatedRegs_;
    221  AllocatableRegisterSet availableRegs_;
    222 
    223  // Whether there might be multiple entries in allocatedRegs_ for the same
    224  // vreg. This can happen for instance if an instruction has multiple fixed
    225  // register uses for the same vreg.
    226  //
    227  // This is uncommon and we try to avoid it, but if it does happen we fix
    228  // it up at the end of allocateForInstruction.
    229  bool hasMultipleRegsForVreg_ = false;
    230 
    231  // All registers allocated for the current LIR instruction.
    232  AllocatableRegisterSet currentInsRegs_;
    233 
    234  // Like currentInsRegs_, but does not include registers that are only used
    235  // for at-start uses.
    236  AllocatableRegisterSet currentInsRegsNotAtStart_;
    237 
    238  // Registers required for fixed outputs/temps of the current LIR instruction.
    239  AllocatableRegisterSet fixedTempRegs_;
    240  AllocatableRegisterSet fixedOutputAndTempRegs_;
    241 
    242  // The set of live GC things at the start of each basic block. Although the
    243  // VirtualRegBitSet may contain malloced memory, all are owned by the
    244  // SimpleAllocator whose destructor will destroy them.
    245  using VirtualRegBitSet =
    246      SparseBitSet<BackgroundSystemAllocPolicy, SimpleAllocator>;
    247  Vector<VirtualRegBitSet, 0, JitAllocPolicy> liveGCIn_;
    248 
    249  // Vector sorted by instructionId in descending order. This is used by
    250  // freeDeadVregsAfterInstruction to mark virtual registers as dead after their
    251  // last use.
    252  struct VregLastUse {
    253    uint32_t instructionId;
    254    uint32_t vregId;
    255    VregLastUse(uint32_t instructionId, uint32_t vregId)
    256        : instructionId(instructionId), vregId(vregId) {}
    257  };
    258  Vector<VregLastUse, 0, JitAllocPolicy> vregLastUses_;
    259 
    260  // Vector used for spilling instruction outputs that are used across blocks.
    261  Vector<LDefinition*, 4, BackgroundSystemAllocPolicy> eagerSpillOutputs_;
    262 
    263  // Allocator used to allocate and free stack slots.
    264  StackSlotAllocator stackSlotAllocator_;
    265 
    266  // Register state for a small number of previous blocks. Blocks with a single
    267  // predecessor block can optionally reuse this state.
    268  //
    269  // BlockStateLength should be small enough so that iterating over the array is
    270  // very cheap. It's currently 4 and this was sufficient for a hit rate of
    271  // 88.6% on a very large Wasm module.
    272  static constexpr size_t BlockStateLength = 4;
    273  struct BlockState {
    274    uint32_t blockIndex = UINT32_MAX;
    275    AllocatedRegisterVector allocatedRegs;
    276    AllocatableRegisterSet availableRegs;
    277  };
    278  mozilla::Array<BlockState, BlockStateLength> blockStates_;
    279  size_t nextBlockStateIndex_ = 0;
    280 
    281  // Data used to insert a move for the input of a MUST_REUSE_INPUT definition.
    282  struct ReusedInputReg {
    283    LAllocation* source;
    284    AnyRegister dest;
    285    LDefinition::Type type;
    286    ReusedInputReg(LAllocation* source, AnyRegister dest,
    287                   LDefinition::Type type)
    288        : source(source), dest(dest), type(type) {}
    289  };
    290  Vector<ReusedInputReg, 4, BackgroundSystemAllocPolicy> reusedInputs_;
    291 
    292  enum class AllocationKind { UseAtStart, Output, UseOrTemp };
    293 
    294  [[nodiscard]] bool addAllocatedReg(LInstruction* ins, uint32_t vregId,
    295                                     bool usedAtStart, AnyRegister reg) {
    296    currentInsRegs_.addUnchecked(reg);
    297    if (!usedAtStart) {
    298      currentInsRegsNotAtStart_.addUnchecked(reg);
    299    }
    300    availableRegs_.take(reg);
    301    MOZ_ASSERT_IF(vregs_[vregId].hasRegister(), hasMultipleRegsForVreg_);
    302    vregs_[vregId].setRegisterIndex(allocatedRegs_.length());
    303    return allocatedRegs_.emplaceBack(vregId, reg, ins->id());
    304  }
    305  void markUseOfAllocatedReg(LInstruction* ins, AllocatedRegister& allocated,
    306                             bool usedAtStart) {
    307    MOZ_ASSERT(!availableRegs_.has(allocated.reg()));
    308    allocated.setLastUsedAtInsId(ins->id());
    309    currentInsRegs_.addUnchecked(allocated.reg());
    310    if (!usedAtStart) {
    311      currentInsRegsNotAtStart_.addUnchecked(allocated.reg());
    312    }
    313  }
    314 
    315  [[nodiscard]] bool init();
    316  [[nodiscard]] bool analyzeLiveness();
    317  [[nodiscard]] bool allocateRegisters();
    318 
    319  void removeAllocatedRegisterAtIndex(size_t index);
    320 
    321  [[nodiscard]] bool tryReuseRegistersFromPredecessor(MBasicBlock* block);
    322  void saveAndClearAllocatedRegisters(MBasicBlock* block);
    323 
    324  void freeDeadVregsAfterInstruction(VirtualRegBitSet& liveGC, LNode* ins);
    325 
    326  [[nodiscard]] bool allocateForBlockEnd(LBlock* block, LInstruction* ins);
    327 
    328  LAllocation ensureStackLocation(uint32_t vregId);
    329  LAllocation registerOrStackLocation(LInstruction* ins, uint32_t vregId,
    330                                      bool trackRegUse);
    331 
    332  [[nodiscard]] bool spillRegister(LInstruction* ins,
    333                                   AllocatedRegister allocated);
    334 
    335  [[nodiscard]] bool evictRegister(LInstruction* ins, AnyRegister reg);
    336 
    337  void scanDefinition(LInstruction* ins, LDefinition* def, bool isTemp);
    338 
    339  [[nodiscard]] bool allocateForNonFixedDefOrUse(LInstruction* ins,
    340                                                 uint32_t vregId,
    341                                                 AllocationKind kind,
    342                                                 AnyRegister* reg);
    343  [[nodiscard]] bool allocateForFixedUse(LInstruction* ins, const LUse* use,
    344                                         AnyRegister* reg);
    345  [[nodiscard]] bool allocateForRegisterUse(LInstruction* ins, const LUse* use,
    346                                            AnyRegister* reg);
    347 
    348  [[nodiscard]] bool allocateForDefinition(uint32_t blockLastId,
    349                                           LInstruction* ins, LDefinition* def,
    350                                           bool isTemp);
    351  [[nodiscard]] bool allocateForInstruction(VirtualRegBitSet& liveGC,
    352                                            uint32_t blockLastId,
    353                                            LInstruction* ins);
    354 
    355  [[nodiscard]] bool addLiveRegisterToSafepoint(LSafepoint* safepoint,
    356                                                AllocatedRegister allocated);
    357  [[nodiscard]] bool populateSafepoint(VirtualRegBitSet& liveGC,
    358                                       LInstruction* ins,
    359                                       LSafepoint* safepoint);
    360 
    361  void assertValidRegisterStateBeforeInstruction() const;
    362 
    363 public:
    364  SimpleAllocator(MIRGenerator* mir, LIRGenerator* lir, LIRGraph& graph)
    365      : RegisterAllocator(mir, lir, graph),
    366        vregs_(mir->alloc()),
    367        liveGCIn_(mir->alloc()),
    368        vregLastUses_(mir->alloc()) {}
    369 
    370  [[nodiscard]] bool go();
    371 };
    372 
    373 }  // namespace jit
    374 }  // namespace js
    375 
    376 #endif /* jit_SimpleAllocator_h */