diff --git a/BUILD.gn b/BUILD.gn index 01a73b7c23..8c3b0c7780 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -2519,6 +2519,8 @@ v8_source_set("v8_base_without_compiler") { "src/builtins/constants-table-builder.cc", "src/builtins/constants-table-builder.h", "src/builtins/profile-data-reader.h", + "src/codegen/aligned-slot-allocator.cc", + "src/codegen/aligned-slot-allocator.h", "src/codegen/assembler-arch.h", "src/codegen/assembler-inl.h", "src/codegen/assembler.cc", @@ -4384,6 +4386,7 @@ v8_component("v8_libbase") { if (is_tsan && !build_with_chromium) { data += [ "tools/sanitizers/tsan_suppressions.txt" ] + # llvm-symbolizer uses libstdc++ from the clang package. data += [ "//third_party/llvm-build/Release+Asserts/lib/libstdc++.so.6" ] } diff --git a/src/codegen/aligned-slot-allocator.cc b/src/codegen/aligned-slot-allocator.cc new file mode 100644 index 0000000000..83824f001d --- /dev/null +++ b/src/codegen/aligned-slot-allocator.cc @@ -0,0 +1,128 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/codegen/aligned-slot-allocator.h" + +#include "src/base/bits.h" +#include "src/base/logging.h" + +namespace v8 { +namespace internal { + +// Define storage so we can use things like std::min that may take an address. +constexpr int AlignedSlotAllocator::kSlotSize; + +int AlignedSlotAllocator::NextSlot(int n) const { + DCHECK(n == 1 || n == 2 || n == 4); + if (n <= 1 && IsValid(next1_)) return next1_; + if (n <= 2 && IsValid(next2_)) return next2_; + DCHECK(IsValid(next4_)); + return next4_; +} + +int AlignedSlotAllocator::Allocate(int n) { + DCHECK(n == 1 || n == 2 || n == 4); + // Check invariants. + DCHECK_EQ(0, next4_ & 3); + DCHECK_IMPLIES(IsValid(next2_), (next2_ & 1) == 0); + + // The sentinel value kInvalidSlot is used to indicate no slot. + // next1_ is the index of the 1 slot fragment, or kInvalidSlot. + // next2_ is the 2-aligned index of the 2 slot fragment, or kInvalidSlot. + // next4_ is the 4-aligned index of the next 4 slot group. It is always valid. + // In order to ensure we only have a single 1- or 2-slot fragment, we greedily + // use any fragment that satisfies the request. + int result = kInvalidSlot; + switch (n) { + case 1: { + if (IsValid(next1_)) { + result = next1_; + next1_ = kInvalidSlot; + } else if (IsValid(next2_)) { + result = next2_; + next1_ = result + 1; + next2_ = kInvalidSlot; + } else { + result = next4_; + next1_ = result + 1; + next2_ = result + 2; + next4_ += 4; + } + break; + } + case 2: { + if (IsValid(next2_)) { + result = next2_; + next2_ = kInvalidSlot; + } else { + result = next4_; + next2_ = result + 2; + next4_ += 4; + } + break; + } + case 4: { + result = next4_; + next4_ += 4; + break; + } + default: + UNREACHABLE(); + break; + } + DCHECK(IsValid(result)); + size_ = std::max(size_, result + n); + return result; +} + +int AlignedSlotAllocator::AllocateUnaligned(int n) { + DCHECK_GE(n, 0); + // Check invariants. + DCHECK_EQ(0, next4_ & 3); + DCHECK_IMPLIES(IsValid(next2_), (next2_ & 1) == 0); + + // Reserve |n| slots at |size_|, invalidate fragments below the new |size_|, + // and add any new fragments beyond the new |size_|. + int result = size_; + size_ += n; + switch (size_ & 3) { + case 0: { + next1_ = next2_ = kInvalidSlot; + next4_ = size_; + break; + } + case 1: { + next1_ = size_; + next2_ = size_ + 1; + next4_ = size_ + 3; + break; + } + case 2: { + next1_ = kInvalidSlot; + next2_ = size_; + next4_ = size_ + 2; + break; + } + case 3: { + next1_ = size_; + next2_ = kInvalidSlot; + next4_ = size_ + 1; + break; + } + } + return result; +} + +int AlignedSlotAllocator::Align(int n) { + DCHECK(base::bits::IsPowerOfTwo(n)); + DCHECK_LE(n, 4); + int mask = n - 1; + int misalignment = size_ & mask; + int padding = (n - misalignment) & mask; + AllocateUnaligned(padding); + return padding; +} + +} // namespace internal +} // namespace v8 diff --git a/src/codegen/aligned-slot-allocator.h b/src/codegen/aligned-slot-allocator.h new file mode 100644 index 0000000000..1abb711713 --- /dev/null +++ b/src/codegen/aligned-slot-allocator.h @@ -0,0 +1,71 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CODEGEN_ALIGNED_SLOT_ALLOCATOR_H_ +#define V8_CODEGEN_ALIGNED_SLOT_ALLOCATOR_H_ + +#include "src/base/macros.h" +#include "src/base/platform/platform.h" +#include "src/common/globals.h" + +namespace v8 { +namespace internal { + +// An aligned slot allocator. Allocates groups of 1, 2, or 4 slots such that the +// first slot of the group is aligned to the group size. The allocator can also +// allocate unaligned groups of arbitrary size, and an align the number of slots +// to 1, 2, or 4 slots. The allocator tries to be as thrifty as possible by +// reusing alignment padding slots in subsequent smaller slot allocations. +class V8_EXPORT_PRIVATE AlignedSlotAllocator { + public: + // Slots are always multiples of pointer-sized units. + static constexpr int kSlotSize = kSystemPointerSize; + + static int NumSlotsForWidth(int bytes) { + DCHECK_GT(bytes, 0); + return (bytes + kSlotSize - 1) / kSlotSize; + } + + AlignedSlotAllocator() = default; + + // Allocates |n| slots, where |n| must be 1, 2, or 4. Padding slots may be + // inserted for alignment. + // Returns the starting index of the slots, which is evenly divisible by |n|. + int Allocate(int n); + + // Gets the starting index of the slots that would be returned by Allocate(n). + int NextSlot(int n) const; + + // Allocates the given number of slots at the current end of the slot area, + // and returns the starting index of the slots. This resets any fragment + // slots, so subsequent allocations will be after the end of this one. + // AllocateUnaligned(0) can be used to partition the slot area, for example + // to make sure tagged values follow untagged values on a Frame. + int AllocateUnaligned(int n); + + // Aligns the slot area so that future allocations begin at the alignment. + // Returns the number of slots needed to align the slot area. + int Align(int n); + + // Returns the size of the slot area, in slots. This will be greater than any + // already allocated slot index. + int Size() const { return size_; } + + private: + static constexpr int kInvalidSlot = -1; + + static bool IsValid(int slot) { return slot > kInvalidSlot; } + + int next1_ = kInvalidSlot; + int next2_ = kInvalidSlot; + int next4_ = 0; + int size_ = 0; + + DISALLOW_NEW_AND_DELETE() +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_CODEGEN_ALIGNED_SLOT_ALLOCATOR_H_ diff --git a/src/compiler/backend/arm/instruction-selector-arm.cc b/src/compiler/backend/arm/instruction-selector-arm.cc index 6a1a1f637d..78a458083d 100644 --- a/src/compiler/backend/arm/instruction-selector-arm.cc +++ b/src/compiler/backend/arm/instruction-selector-arm.cc @@ -499,7 +499,7 @@ void VisitPairAtomicBinOp(InstructionSelector* selector, Node* node, void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/backend/arm64/instruction-selector-arm64.cc b/src/compiler/backend/arm64/instruction-selector-arm64.cc index b041310371..7979ee697e 100644 --- a/src/compiler/backend/arm64/instruction-selector-arm64.cc +++ b/src/compiler/backend/arm64/instruction-selector-arm64.cc @@ -566,7 +566,7 @@ int32_t LeftShiftForReducedMultiply(Matcher* m) { void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/backend/ia32/instruction-selector-ia32.cc b/src/compiler/backend/ia32/instruction-selector-ia32.cc index 8c89337d44..452ad2f4fe 100644 --- a/src/compiler/backend/ia32/instruction-selector-ia32.cc +++ b/src/compiler/backend/ia32/instruction-selector-ia32.cc @@ -398,7 +398,7 @@ void VisitRROI8x16SimdShift(InstructionSelector* selector, Node* node, void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/backend/ppc/instruction-selector-ppc.cc b/src/compiler/backend/ppc/instruction-selector-ppc.cc index cbd9038e5a..693be1706b 100644 --- a/src/compiler/backend/ppc/instruction-selector-ppc.cc +++ b/src/compiler/backend/ppc/instruction-selector-ppc.cc @@ -155,7 +155,7 @@ void VisitBinop(InstructionSelector* selector, Node* node, void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/backend/register-allocator.cc b/src/compiler/backend/register-allocator.cc index 84145c8779..71e121661d 100644 --- a/src/compiler/backend/register-allocator.cc +++ b/src/compiler/backend/register-allocator.cc @@ -4519,9 +4519,11 @@ void OperandAssigner::AssignSpillSlots() { for (SpillRange* range : spill_ranges) { data()->tick_counter()->TickAndMaybeEnterSafepoint(); if (range == nullptr || range->IsEmpty()) continue; - // Allocate a new operand referring to the spill slot. if (!range->HasSlot()) { - int index = data()->frame()->AllocateSpillSlot(range->byte_width()); + // Allocate a new operand referring to the spill slot, aligned to the + // operand size. + int width = range->byte_width(); + int index = data()->frame()->AllocateSpillSlot(width, width); range->set_assigned_slot(index); } } diff --git a/src/compiler/backend/s390/instruction-selector-s390.cc b/src/compiler/backend/s390/instruction-selector-s390.cc index e0b6b4f936..249838f713 100644 --- a/src/compiler/backend/s390/instruction-selector-s390.cc +++ b/src/compiler/backend/s390/instruction-selector-s390.cc @@ -680,7 +680,7 @@ void VisitBinOp(InstructionSelector* selector, Node* node, void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/backend/x64/instruction-selector-x64.cc b/src/compiler/backend/x64/instruction-selector-x64.cc index f6c3e020e5..f02a2759a2 100644 --- a/src/compiler/backend/x64/instruction-selector-x64.cc +++ b/src/compiler/backend/x64/instruction-selector-x64.cc @@ -336,7 +336,7 @@ ArchOpcode GetStoreOpcode(StoreRepresentation store_rep) { void InstructionSelector::VisitStackSlot(Node* node) { StackSlotRepresentation rep = StackSlotRepresentationOf(node->op()); - int slot = frame_->AllocateSpillSlot(rep.size()); + int slot = frame_->AllocateSpillSlot(rep.size(), rep.alignment()); OperandGenerator g(this); Emit(kArchStackSlot, g.DefineAsRegister(node), diff --git a/src/compiler/frame.cc b/src/compiler/frame.cc index 98938e8358..59b3c174c7 100644 --- a/src/compiler/frame.cc +++ b/src/compiler/frame.cc @@ -12,14 +12,15 @@ namespace compiler { Frame::Frame(int fixed_frame_size_in_slots) : fixed_slot_count_(fixed_frame_size_in_slots), - frame_slot_count_(fixed_frame_size_in_slots), spill_slot_count_(0), return_slot_count_(0), allocated_registers_(nullptr), - allocated_double_registers_(nullptr) {} + allocated_double_registers_(nullptr) { + slot_allocator_.AllocateUnaligned(fixed_frame_size_in_slots); +} void Frame::AlignFrame(int alignment) { - int alignment_slots = alignment / kSystemPointerSize; + int alignment_slots = AlignedSlotAllocator::NumSlotsForWidth(alignment); // In the calculations below we assume that alignment_slots is a power of 2. DCHECK(base::bits::IsPowerOfTwo(alignment_slots)); @@ -28,11 +29,12 @@ void Frame::AlignFrame(int alignment) { int return_delta = alignment_slots - (return_slot_count_ & (alignment_slots - 1)); if (return_delta != alignment_slots) { - frame_slot_count_ += return_delta; + slot_allocator_.Align(alignment_slots); } - int delta = alignment_slots - (frame_slot_count_ & (alignment_slots - 1)); + int delta = + alignment_slots - (slot_allocator_.Size() & (alignment_slots - 1)); if (delta != alignment_slots) { - frame_slot_count_ += delta; + slot_allocator_.Align(alignment_slots); if (spill_slot_count_ != 0) { spill_slot_count_ += delta; } diff --git a/src/compiler/frame.h b/src/compiler/frame.h index 1b03a22968..c53ccfd777 100644 --- a/src/compiler/frame.h +++ b/src/compiler/frame.h @@ -5,6 +5,7 @@ #ifndef V8_COMPILER_FRAME_H_ #define V8_COMPILER_FRAME_H_ +#include "src/codegen/aligned-slot-allocator.h" #include "src/execution/frame-constants.h" #include "src/utils/bit-vector.h" @@ -92,7 +93,7 @@ class V8_EXPORT_PRIVATE Frame : public ZoneObject { Frame(const Frame&) = delete; Frame& operator=(const Frame&) = delete; - inline int GetTotalFrameSlotCount() const { return frame_slot_count_; } + inline int GetTotalFrameSlotCount() const { return slot_allocator_.Size(); } inline int GetFixedSlotCount() const { return fixed_slot_count_; } inline int GetSpillSlotCount() const { return spill_slot_count_; } inline int GetReturnSlotCount() const { return return_slot_count_; } @@ -112,36 +113,47 @@ class V8_EXPORT_PRIVATE Frame : public ZoneObject { } void AlignSavedCalleeRegisterSlots(int alignment = kDoubleSize) { - int alignment_slots = alignment / kSystemPointerSize; - int delta = alignment_slots - (frame_slot_count_ & (alignment_slots - 1)); - if (delta != alignment_slots) { - frame_slot_count_ += delta; - } - spill_slot_count_ += delta; + int alignment_slots = AlignedSlotAllocator::NumSlotsForWidth(alignment); + int padding = slot_allocator_.Align(alignment_slots); + spill_slot_count_ += padding; } void AllocateSavedCalleeRegisterSlots(int count) { - frame_slot_count_ += count; + slot_allocator_.AllocateUnaligned(count); } int AllocateSpillSlot(int width, int alignment = 0) { - DCHECK_EQ(frame_slot_count_, + DCHECK_EQ(GetTotalFrameSlotCount(), fixed_slot_count_ + spill_slot_count_ + return_slot_count_); - int frame_slot_count_before = frame_slot_count_; - if (alignment > kSystemPointerSize) { - // Slots are pointer sized, so alignment greater than a pointer size - // requires allocating additional slots. - width += alignment - kSystemPointerSize; + // TODO(bbudge) Add unit tests for this class. + int actual_width = std::max(width, AlignedSlotAllocator::kSlotSize); + int actual_alignment = std::max(alignment, AlignedSlotAllocator::kSlotSize); + int slots = AlignedSlotAllocator::NumSlotsForWidth(actual_width); + int old_end = slot_allocator_.Size(); + int slot; + if (actual_width == actual_alignment) { + // Simple allocation, alignment equal to width. + slot = slot_allocator_.Allocate(slots); + } else { + // Complex allocation, alignment different from width. + if (actual_alignment > AlignedSlotAllocator::kSlotSize) { + // Alignment required. + int alignment_in_slots = + AlignedSlotAllocator::NumSlotsForWidth(actual_alignment); + slot_allocator_.Align(alignment_in_slots); + } + slot = slot_allocator_.AllocateUnaligned(slots); } - AllocateAlignedFrameSlots(width); - spill_slot_count_ += frame_slot_count_ - frame_slot_count_before; - return frame_slot_count_ - return_slot_count_ - 1; + int end = slot_allocator_.Size(); + + spill_slot_count_ += end - old_end; + return slot + slots - return_slot_count_ - 1; } void EnsureReturnSlots(int count) { if (count > return_slot_count_) { count -= return_slot_count_; - frame_slot_count_ += count; + slot_allocator_.AllocateUnaligned(count); return_slot_count_ += count; } } @@ -151,28 +163,15 @@ class V8_EXPORT_PRIVATE Frame : public ZoneObject { int ReserveSpillSlots(size_t slot_count) { DCHECK_EQ(0, spill_slot_count_); spill_slot_count_ += static_cast(slot_count); - frame_slot_count_ += static_cast(slot_count); - return frame_slot_count_ - 1; - } - - private: - void AllocateAlignedFrameSlots(int width) { - DCHECK_LT(0, width); - int new_frame_slots = (width + kSystemPointerSize - 1) / kSystemPointerSize; - // Align to 8 bytes if width is a multiple of 8 bytes, and to 16 bytes if - // multiple of 16. - int align_to = - (width & 15) == 0 ? 16 : (width & 7) == 0 ? 8 : kSystemPointerSize; - frame_slot_count_ = RoundUp(frame_slot_count_ + new_frame_slots, - align_to / kSystemPointerSize); - DCHECK_LT(0, frame_slot_count_); + slot_allocator_.AllocateUnaligned(static_cast(slot_count)); + return slot_allocator_.Size() - 1; } private: int fixed_slot_count_; - int frame_slot_count_; int spill_slot_count_; int return_slot_count_; + AlignedSlotAllocator slot_allocator_; BitVector* allocated_registers_; BitVector* allocated_double_registers_; }; diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index a3e305da8f..c24f3a2706 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -8069,6 +8069,7 @@ class LinkageLocationAllocator { void SetStackOffset(int offset) { allocator_.SetStackOffset(offset); } int NumStackSlots() const { return allocator_.NumStackSlots(); } + void EndSlotArea() { allocator_.EndSlotArea(); } private: wasm::LinkageAllocator allocator_; @@ -8107,6 +8108,10 @@ CallDescriptor* GetWasmCallDescriptor( auto l = params.Next(param); locations.AddParamAt(i + param_offset, l); } + + // End the untagged area, so tagged slots come after. + params.EndSlotArea(); + for (size_t i = 0; i < parameter_count; i++) { MachineRepresentation param = fsig->GetParam(i).machine_representation(); // Skip untagged parameters. diff --git a/src/wasm/wasm-linkage.h b/src/wasm/wasm-linkage.h index 7e56ea6eae..a2de3426d3 100644 --- a/src/wasm/wasm-linkage.h +++ b/src/wasm/wasm-linkage.h @@ -5,6 +5,7 @@ #ifndef V8_WASM_WASM_LINKAGE_H_ #define V8_WASM_WASM_LINKAGE_H_ +#include "src/codegen/aligned-slot-allocator.h" #include "src/codegen/assembler-arch.h" #include "src/codegen/machine-type.h" #include "src/codegen/signature.h" @@ -44,7 +45,7 @@ constexpr DoubleRegister kFpReturnRegisters[] = {xmm1, xmm2}; // =========================================================================== constexpr Register kGpParamRegisters[] = {r3, r0, r2, r6}; constexpr Register kGpReturnRegisters[] = {r0, r1}; -// ARM d-registers must be in ascending order for correct allocation. +// ARM d-registers must be in even/odd D-register pairs for correct allocation. constexpr DoubleRegister kFpParamRegisters[] = {d0, d1, d2, d3, d4, d5, d6, d7}; constexpr DoubleRegister kFpReturnRegisters[] = {d0, d1}; @@ -133,18 +134,27 @@ class LinkageAllocator { bool CanAllocateFP(MachineRepresentation rep) const { #if V8_TARGET_ARCH_ARM switch (rep) { - case MachineRepresentation::kFloat32: - return fp_offset_ < fp_count_ && fp_regs_[fp_offset_].code() < 16; - case MachineRepresentation::kFloat64: - return extra_double_reg_ >= 0 || fp_offset_ < fp_count_; - case MachineRepresentation::kSimd128: - return ((fp_offset_ + 1) & ~1) + 1 < fp_count_; + case MachineRepresentation::kFloat32: { + // Get the next D-register (Liftoff only uses the even S-registers). + int next = fp_allocator_.NextSlot(2) / 2; + // Only the lower 16 D-registers alias S-registers. + return next < fp_count_ && fp_regs_[next].code() < 16; + } + case MachineRepresentation::kFloat64: { + int next = fp_allocator_.NextSlot(2) / 2; + return next < fp_count_; + } + case MachineRepresentation::kSimd128: { + int next = fp_allocator_.NextSlot(4) / 2; + return next < fp_count_ - 1; // 2 D-registers are required. + } default: UNREACHABLE(); return false; } -#endif +#else return fp_offset_ < fp_count_; +#endif } int NextGpReg() { @@ -153,80 +163,58 @@ class LinkageAllocator { } int NextFpReg(MachineRepresentation rep) { + DCHECK(CanAllocateFP(rep)); #if V8_TARGET_ARCH_ARM switch (rep) { case MachineRepresentation::kFloat32: { - // Liftoff uses only even-numbered f32 registers, and encodes them using - // the code of the corresponding f64 register. This limits the calling - // interface to only using the even-numbered f32 registers. + // Liftoff uses only even-numbered S-registers, and encodes them using + // the code of the corresponding D-register. This limits the calling + // interface to only using the even-numbered S-registers. int d_reg_code = NextFpReg(MachineRepresentation::kFloat64); - DCHECK_GT(16, d_reg_code); // D-registers 16 - 31 can't split. + DCHECK_GT(16, d_reg_code); // D16 - D31 don't alias S-registers. return d_reg_code * 2; } case MachineRepresentation::kFloat64: { - // Use the extra D-register if there is one. - if (extra_double_reg_ >= 0) { - int reg_code = extra_double_reg_; - extra_double_reg_ = -1; - return reg_code; - } - DCHECK_LT(fp_offset_, fp_count_); - return fp_regs_[fp_offset_++].code(); + int next = fp_allocator_.Allocate(2) / 2; + return fp_regs_[next].code(); } case MachineRepresentation::kSimd128: { - // Q-register must be an even-odd pair, so we must try to allocate at - // the end, not using extra_double_reg_. If we are at an odd D-register, - // skip past it (saving it to extra_double_reg_). - DCHECK_LT(((fp_offset_ + 1) & ~1) + 1, fp_count_); - int d_reg1_code = fp_regs_[fp_offset_++].code(); - if (d_reg1_code % 2 != 0) { - // If we're misaligned then extra_double_reg_ must have been consumed. - DCHECK_EQ(-1, extra_double_reg_); - int odd_double_reg = d_reg1_code; - d_reg1_code = fp_regs_[fp_offset_++].code(); - extra_double_reg_ = odd_double_reg; - } - // Combine the current D-register with the next to form a Q-register. - int d_reg2_code = fp_regs_[fp_offset_++].code(); - DCHECK_EQ(0, d_reg1_code % 2); - DCHECK_EQ(d_reg1_code + 1, d_reg2_code); - USE(d_reg2_code); - return d_reg1_code / 2; + int next = fp_allocator_.Allocate(4) / 2; + int d_reg_code = fp_regs_[next].code(); + // Check that result and the next D-register pair. + DCHECK_EQ(0, d_reg_code % 2); + DCHECK_EQ(d_reg_code + 1, fp_regs_[next + 1].code()); + return d_reg_code / 2; } default: UNREACHABLE(); } #else - DCHECK_LT(fp_offset_, fp_count_); return fp_regs_[fp_offset_++].code(); #endif } // Stackslots are counted upwards starting from 0 (or the offset set by - // {SetStackOffset}. - int NumStackSlots(MachineRepresentation type) { - return std::max(1, ElementSizeInBytes(type) / kSystemPointerSize); - } - - // Stackslots are counted upwards starting from 0 (or the offset set by - // {SetStackOffset}. If {type} needs more than - // one stack slot, the lowest used stack slot is returned. + // {SetStackOffset}. If {type} needs more than one stack slot, the lowest + // used stack slot is returned. int NextStackSlot(MachineRepresentation type) { - int num_stack_slots = NumStackSlots(type); - int offset = stack_offset_; - stack_offset_ += num_stack_slots; - return offset; + int num_slots = + AlignedSlotAllocator::NumSlotsForWidth(ElementSizeInBytes(type)); + int slot = slot_allocator_.Allocate(num_slots); + return slot; } // Set an offset for the stack slots returned by {NextStackSlot} and // {NumStackSlots}. Can only be called before any call to {NextStackSlot}. - void SetStackOffset(int num) { - DCHECK_LE(0, num); - DCHECK_EQ(0, stack_offset_); - stack_offset_ = num; + void SetStackOffset(int offset) { + DCHECK_LE(0, offset); + DCHECK_EQ(0, slot_allocator_.Size()); + slot_allocator_.AllocateUnaligned(offset); } - int NumStackSlots() const { return stack_offset_; } + int NumStackSlots() const { return slot_allocator_.Size(); } + + void EndSlotArea() { slot_allocator_.AllocateUnaligned(0); } private: const int gp_count_; @@ -234,16 +222,16 @@ class LinkageAllocator { const Register* const gp_regs_; const int fp_count_; +#if V8_TARGET_ARCH_ARM + // Use an aligned slot allocator to model ARM FP register aliasing. The slots + // are 32 bits, so 2 slots are required for a D-register, 4 for a Q-register. + AlignedSlotAllocator fp_allocator_; +#else int fp_offset_ = 0; +#endif const DoubleRegister* const fp_regs_; -#if V8_TARGET_ARCH_ARM - // Track fragments of registers below fp_offset_ here. There can only be one - // extra double register. - int extra_double_reg_ = -1; -#endif - - int stack_offset_ = 0; + AlignedSlotAllocator slot_allocator_; }; } // namespace wasm diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn index d98c5b82c1..586bf29a99 100644 --- a/test/unittests/BUILD.gn +++ b/test/unittests/BUILD.gn @@ -215,6 +215,7 @@ v8_source_set("unittests_sources") { "base/threaded-list-unittest.cc", "base/utils/random-number-generator-unittest.cc", "base/vlq-base64-unittest.cc", + "codegen/aligned-slot-allocator-unittest.cc", "codegen/code-stub-assembler-unittest.cc", "codegen/code-stub-assembler-unittest.h", "codegen/register-configuration-unittest.cc", diff --git a/test/unittests/codegen/aligned-slot-allocator-unittest.cc b/test/unittests/codegen/aligned-slot-allocator-unittest.cc new file mode 100644 index 0000000000..2601b23a57 --- /dev/null +++ b/test/unittests/codegen/aligned-slot-allocator-unittest.cc @@ -0,0 +1,167 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/codegen/aligned-slot-allocator.h" + +#include "src/base/bits.h" +#include "testing/gtest-support.h" + +namespace v8 { +namespace internal { + +class AlignedSlotAllocatorUnitTest : public ::testing::Test { + public: + AlignedSlotAllocatorUnitTest() = default; + ~AlignedSlotAllocatorUnitTest() override = default; + + // Helper method to test AlignedSlotAllocator::Allocate. + void Allocate(int size, int expected) { + int next = allocator_.NextSlot(size); + int result = allocator_.Allocate(size); + EXPECT_EQ(next, result); // NextSlot/Allocate are consistent. + EXPECT_EQ(expected, result); + EXPECT_EQ(0, result & (size - 1)); // result is aligned to size. + int slot_end = result + static_cast(base::bits::RoundUpToPowerOfTwo32( + static_cast(size))); + EXPECT_LE(slot_end, allocator_.Size()); // allocator Size is beyond slot. + } + + // Helper method to test AlignedSlotAllocator::AllocateUnaligned. + void AllocateUnaligned(int size, int expected, int expected1, int expected2, + int expected4) { + int size_before = allocator_.Size(); + int result = allocator_.AllocateUnaligned(size); + EXPECT_EQ(size_before, result); // AllocateUnaligned/Size are consistent. + EXPECT_EQ(expected, result); + EXPECT_EQ(result + size, allocator_.Size()); + EXPECT_EQ(expected1, allocator_.NextSlot(1)); + EXPECT_EQ(expected2, allocator_.NextSlot(2)); + EXPECT_EQ(expected4, allocator_.NextSlot(4)); + } + + AlignedSlotAllocator allocator_; +}; + +TEST_F(AlignedSlotAllocatorUnitTest, Allocate1) { + Allocate(1, 0); + EXPECT_EQ(2, allocator_.NextSlot(2)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + Allocate(1, 1); + EXPECT_EQ(2, allocator_.NextSlot(2)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + Allocate(1, 2); + EXPECT_EQ(4, allocator_.NextSlot(2)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + Allocate(1, 3); + EXPECT_EQ(4, allocator_.NextSlot(2)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + // Make sure we use 1-fragments. + Allocate(1, 4); + Allocate(2, 6); + Allocate(1, 5); + + // Make sure we use 2-fragments. + Allocate(2, 8); + Allocate(1, 10); + Allocate(1, 11); +} + +TEST_F(AlignedSlotAllocatorUnitTest, Allocate2) { + Allocate(2, 0); + EXPECT_EQ(2, allocator_.NextSlot(1)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + Allocate(2, 2); + EXPECT_EQ(4, allocator_.NextSlot(1)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + + // Make sure we use 2-fragments. + Allocate(1, 4); + Allocate(2, 6); + Allocate(2, 8); +} + +TEST_F(AlignedSlotAllocatorUnitTest, Allocate4) { + Allocate(4, 0); + EXPECT_EQ(4, allocator_.NextSlot(1)); + EXPECT_EQ(4, allocator_.NextSlot(2)); + + Allocate(1, 4); + Allocate(4, 8); + + Allocate(2, 6); + Allocate(4, 12); +} + +TEST_F(AlignedSlotAllocatorUnitTest, AllocateUnaligned) { + AllocateUnaligned(1, 0, 1, 2, 4); + AllocateUnaligned(1, 1, 2, 2, 4); + + Allocate(1, 2); + + AllocateUnaligned(2, 3, 5, 6, 8); + + // Advance to leave 1- and 2- fragments below Size. + Allocate(4, 8); + + // AllocateUnaligned should allocate at the end, and clear fragments. + AllocateUnaligned(0, 12, 12, 12, 12); +} + +TEST_F(AlignedSlotAllocatorUnitTest, LargeAllocateUnaligned) { + AllocateUnaligned(11, 0, 11, 12, 12); + AllocateUnaligned(11, 11, 22, 22, 24); + AllocateUnaligned(13, 22, 35, 36, 36); +} + +TEST_F(AlignedSlotAllocatorUnitTest, Size) { + allocator_.Allocate(1); + EXPECT_EQ(1, allocator_.Size()); + // Allocate 2, leaving a fragment at 1. Size should be at 4. + allocator_.Allocate(2); + EXPECT_EQ(4, allocator_.Size()); + // Allocate should consume fragment. + EXPECT_EQ(1, allocator_.Allocate(1)); + // Size should still be 4. + EXPECT_EQ(4, allocator_.Size()); +} + +TEST_F(AlignedSlotAllocatorUnitTest, Align) { + EXPECT_EQ(0, allocator_.Align(1)); + EXPECT_EQ(0, allocator_.Size()); + + // Allocate 1 to become misaligned. + Allocate(1, 0); + + // 4-align. + EXPECT_EQ(3, allocator_.Align(4)); + EXPECT_EQ(4, allocator_.NextSlot(1)); + EXPECT_EQ(4, allocator_.NextSlot(2)); + EXPECT_EQ(4, allocator_.NextSlot(4)); + EXPECT_EQ(4, allocator_.Size()); + + // Allocate 2 to become misaligned. + Allocate(2, 4); + + // 4-align. + EXPECT_EQ(2, allocator_.Align(4)); + EXPECT_EQ(8, allocator_.NextSlot(1)); + EXPECT_EQ(8, allocator_.NextSlot(2)); + EXPECT_EQ(8, allocator_.NextSlot(4)); + EXPECT_EQ(8, allocator_.Size()); + + // No change when we're already aligned. + EXPECT_EQ(0, allocator_.Align(2)); + EXPECT_EQ(8, allocator_.NextSlot(1)); + EXPECT_EQ(8, allocator_.NextSlot(2)); + EXPECT_EQ(8, allocator_.NextSlot(4)); + EXPECT_EQ(8, allocator_.Size()); +} + +} // namespace internal +} // namespace v8