[codegen] Avoid unused fields in safepoint table

Many safepoint tables do not contain any deoptimization info and/or no
callee-saved registers. Do not emit empty fields for all entries in this
case.
This often shrinks the size of the encoded safepoint table by more than
50%.

Drive-by cleanups:
- Rename fields of the safepoint table entries to clarify their meaning
("tagged slots" instead of "bits", "tagged register indexes" instead of
 "register bits").
- Include the PC in the decoded {SafepointEntry} to make it the single
source of truth.

R=jkummerow@chromium.org

Bug: v8:12401
Change-Id: If5c24a688a434842ed3b6427f5f1f3ea9232173a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3289173
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78021}
This commit is contained in:
Clemens Backes 2021-11-19 10:15:16 +01:00 committed by V8 LUCI CQ
parent 9a13c49fd4
commit 0580829fb5
5 changed files with 163 additions and 171 deletions

View File

@ -20,29 +20,28 @@ namespace internal {
SafepointTable::SafepointTable(Isolate* isolate, Address pc, Code code)
: SafepointTable(code.InstructionStart(isolate, pc),
code.SafepointTableAddress(), true) {}
code.SafepointTableAddress()) {}
#if V8_ENABLE_WEBASSEMBLY
SafepointTable::SafepointTable(const wasm::WasmCode* code)
: SafepointTable(code->instruction_start(),
code->instruction_start() + code->safepoint_table_offset(),
false) {}
: SafepointTable(
code->instruction_start(),
code->instruction_start() + code->safepoint_table_offset()) {}
#endif // V8_ENABLE_WEBASSEMBLY
SafepointTable::SafepointTable(Address instruction_start,
Address safepoint_table_address, bool has_deopt)
Address safepoint_table_address)
: instruction_start_(instruction_start),
has_deopt_(has_deopt),
safepoint_table_address_(safepoint_table_address),
length_(ReadLength(safepoint_table_address)),
entry_size_(ReadEntrySize(safepoint_table_address)) {}
length_(base::Memory<int>(safepoint_table_address + kLengthOffset)),
entry_configuration_(base::Memory<uint32_t>(safepoint_table_address +
kEntryConfigurationOffset)) {}
int SafepointTable::find_return_pc(int pc_offset) {
for (int i = 0; i < length(); i++) {
if (GetTrampolinePcOffset(i) == static_cast<int>(pc_offset)) {
return GetPcOffset(i);
} else if (GetPcOffset(i) == pc_offset) {
return pc_offset;
SafepointEntry entry = GetEntry(i);
if (entry.trampoline_pc() == pc_offset || entry.pc() == pc_offset) {
return entry.pc();
}
}
UNREACHABLE();
@ -54,27 +53,21 @@ SafepointEntry SafepointTable::FindEntry(Address pc) const {
DCHECK_NE(kMaxUInt32, pc_offset);
CHECK_LT(0, length_);
// A single entry with pc == -1 covers all call sites in the function.
if (length_ == 1 && GetPcOffset(0) == -1) return GetEntry(0);
if (length_ == 1 && GetEntry(0).pc() == -1) return GetEntry(0);
for (int i = 0; i < length_; i++) {
// TODO(kasperl): Replace the linear search with binary search.
if (GetPcOffset(i) == pc_offset ||
(has_deopt_ &&
GetTrampolinePcOffset(i) == static_cast<int>(pc_offset))) {
return GetEntry(i);
SafepointEntry entry = GetEntry(i);
if (entry.pc() == pc_offset || entry.trampoline_pc() == pc_offset) {
return entry;
}
}
UNREACHABLE();
}
void SafepointTable::PrintEntry(int index, std::ostream& os) const {
disasm::NameConverter converter;
SafepointEntry entry = GetEntry(index);
uint8_t* bits = entry.bits();
// Print the stack slot bits.
for (int i = 0; i < entry_size_; ++i) {
for (uint8_t bits : GetEntry(index).tagged_slots()) {
for (int bit = 0; bit < kBitsPerByte; ++bit) {
os << ((bits[i] & (1 << bit)) ? "1" : "0");
os << ((bits >> bit) & 1);
}
}
}
@ -100,7 +93,7 @@ int SafepointTableBuilder::UpdateDeoptimizationInfo(int pc, int trampoline,
return index;
}
void SafepointTableBuilder::Emit(Assembler* assembler, int bits_per_entry) {
void SafepointTableBuilder::Emit(Assembler* assembler, int tagged_slots_size) {
#ifdef DEBUG
int last_pc = -1;
int last_trampoline = -1;
@ -121,7 +114,7 @@ void SafepointTableBuilder::Emit(Assembler* assembler, int bits_per_entry) {
#endif // DEBUG
RemoveDuplicates();
TrimEntries(&bits_per_entry);
TrimEntries(&tagged_slots_size);
#if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64
// We cannot emit a const pool within the safepoint table.
@ -133,56 +126,58 @@ void SafepointTableBuilder::Emit(Assembler* assembler, int bits_per_entry) {
assembler->RecordComment(";;; Safepoint table.");
offset_ = assembler->pc_offset();
// Compute the number of bytes per safepoint entry.
int bytes_per_entry =
RoundUp(bits_per_entry, kBitsPerByte) >> kBitsPerByteLog2;
// Compute the number of bytes for tagged slots per safepoint entry.
int tagged_slots_bytes =
RoundUp(tagged_slots_size, kBitsPerByte) >> kBitsPerByteLog2;
bool has_deopt_data =
std::any_of(entries_.begin(), entries_.end(), [](auto& entry) {
return entry.deopt_index != SafepointEntry::kNoDeoptIndex;
});
bool has_register_indexes =
std::any_of(entries_.begin(), entries_.end(),
[](auto& entry) { return entry.register_indexes != 0; });
uint32_t entry_configuration =
SafepointTable::TaggedSlotsBytesField::encode(tagged_slots_bytes) |
SafepointTable::HasDeoptDataField::encode(has_deopt_data) |
SafepointTable::HasRegisterIndexesField::encode(has_register_indexes);
// Emit the table header.
STATIC_ASSERT(SafepointTable::kLengthOffset == 0 * kIntSize);
STATIC_ASSERT(SafepointTable::kEntrySizeOffset == 1 * kIntSize);
STATIC_ASSERT(SafepointTable::kEntryConfigurationOffset == 1 * kIntSize);
STATIC_ASSERT(SafepointTable::kHeaderSize == 2 * kIntSize);
int length = static_cast<int>(entries_.size());
assembler->dd(length);
assembler->dd(bytes_per_entry);
assembler->dd(entry_configuration);
// Emit sorted table of pc offsets together with additional info (i.e. the
// deoptimization index or arguments count) and trampoline offsets.
STATIC_ASSERT(SafepointTable::kPcOffset == 0 * kIntSize);
STATIC_ASSERT(SafepointTable::kEncodedInfoOffset == 1 * kIntSize);
STATIC_ASSERT(SafepointTable::kTrampolinePcOffset == 2 * kIntSize);
STATIC_ASSERT(SafepointTable::kFixedEntrySize == 3 * kIntSize);
// Emit entries, sorted by pc offsets.
for (const EntryBuilder& entry : entries_) {
assembler->dd(entry.pc);
if (entry.register_indexes) {
// We emit the register indexes in the same bits as the deopt_index.
// Register indexes and deopt_index should not exist at the same time.
DCHECK_EQ(entry.deopt_index, SafepointEntry::kNoDeoptIndex);
assembler->dd(entry.register_indexes);
} else {
if (has_deopt_data) {
assembler->dd(entry.deopt_index);
assembler->dd(entry.trampoline);
}
if (has_register_indexes) {
assembler->dd(entry.register_indexes);
}
assembler->dd(entry.trampoline);
}
// Emit table of bitmaps.
ZoneVector<uint8_t> bits(bytes_per_entry, 0, zone_);
// Emit bitmaps of tagged stack slots.
ZoneVector<uint8_t> bits(tagged_slots_bytes, 0, zone_);
for (const EntryBuilder& entry : entries_) {
ZoneChunkList<int>* indexes = entry.stack_indexes;
std::fill(bits.begin(), bits.end(), 0);
// Run through the indexes and build a bitmap.
for (int idx : *indexes) {
DCHECK_GT(bits_per_entry, idx);
int index = bits_per_entry - 1 - idx;
for (int idx : *entry.stack_indexes) {
DCHECK_GT(tagged_slots_size, idx);
int index = tagged_slots_size - 1 - idx;
int byte_index = index >> kBitsPerByteLog2;
int bit_index = index & (kBitsPerByte - 1);
bits[byte_index] |= (1U << bit_index);
bits[byte_index] |= (1u << bit_index);
}
// Emit the bitmap for the current entry.
for (int k = 0; k < bytes_per_entry; k++) {
assembler->db(bits[k]);
}
for (uint8_t byte : bits) assembler->db(byte);
}
}
@ -224,13 +219,13 @@ void SafepointTableBuilder::RemoveDuplicates() {
}
}
void SafepointTableBuilder::TrimEntries(int* bits_per_entry) {
int min_index = *bits_per_entry;
void SafepointTableBuilder::TrimEntries(int* tagged_slots_size) {
int min_index = *tagged_slots_size;
if (min_index == 0) return; // Early exit: nothing to trim.
for (auto& entry : entries_) {
for (int idx : *entry.stack_indexes) {
DCHECK_GT(*bits_per_entry, idx); // Validity check.
DCHECK_GT(*tagged_slots_size, idx); // Validity check.
if (idx >= min_index) continue;
if (idx == 0) return; // Early exit: nothing to trim.
min_index = idx;
@ -238,7 +233,7 @@ void SafepointTableBuilder::TrimEntries(int* bits_per_entry) {
}
DCHECK_LT(0, min_index);
*bits_per_entry -= min_index;
*tagged_slots_size -= min_index;
for (auto& entry : entries_) {
for (int& idx : *entry.stack_indexes) {
idx -= min_index;

View File

@ -5,6 +5,7 @@
#ifndef V8_CODEGEN_SAFEPOINT_TABLE_H_
#define V8_CODEGEN_SAFEPOINT_TABLE_H_
#include "src/base/bit-field.h"
#include "src/base/iterator.h"
#include "src/base/memory.h"
#include "src/common/assert-scope.h"
@ -27,66 +28,59 @@ class SafepointEntry {
SafepointEntry() = default;
SafepointEntry(int deopt_index, uint8_t* bits, uint8_t* bits_end,
int trampoline_pc)
: deopt_index_(deopt_index),
bits_(bits),
bits_end_(bits_end),
SafepointEntry(int pc, int deopt_index, uint32_t tagged_register_indexes,
base::Vector<uint8_t> tagged_slots, int trampoline_pc)
: pc_(pc),
deopt_index_(deopt_index),
tagged_register_indexes_(tagged_register_indexes),
tagged_slots_(tagged_slots),
trampoline_pc_(trampoline_pc) {
DCHECK(is_valid());
}
bool is_valid() const { return bits_ != nullptr; }
bool is_valid() const { return tagged_slots_.begin() != nullptr; }
bool Equals(const SafepointEntry& other) const {
return deopt_index_ == other.deopt_index_ && bits_ == other.bits_;
bool operator==(const SafepointEntry& other) const {
return pc_ == other.pc_ && deopt_index_ == other.deopt_index_ &&
tagged_register_indexes_ == other.tagged_register_indexes_ &&
tagged_slots_ == other.tagged_slots_ &&
trampoline_pc_ == other.trampoline_pc_;
}
void Reset() {
deopt_index_ = kNoDeoptIndex;
bits_ = nullptr;
bits_end_ = nullptr;
*this = SafepointEntry{};
DCHECK(!is_valid());
}
int trampoline_pc() { return trampoline_pc_; }
int pc() const { return pc_; }
int deoptimization_index() const {
DCHECK(is_valid() && has_deoptimization_index());
return deopt_index_;
}
uint32_t register_bits() const {
// The register bits use the same field as the deopt_index_.
DCHECK(is_valid());
return deopt_index_;
}
bool has_register_bits() const {
// The register bits use the same field as the deopt_index_.
DCHECK(is_valid());
return deopt_index_ != kNoDeoptIndex;
}
int trampoline_pc() const { return trampoline_pc_; }
bool has_deoptimization_index() const {
DCHECK(is_valid());
return deopt_index_ != kNoDeoptIndex;
}
uint8_t* bits() const {
int deoptimization_index() const {
DCHECK(is_valid() && has_deoptimization_index());
return deopt_index_;
}
uint32_t tagged_register_indexes() const {
DCHECK(is_valid());
return bits_;
return tagged_register_indexes_;
}
base::iterator_range<uint8_t*> iterate_bits() const {
return base::make_iterator_range(bits_, bits_end_);
base::Vector<const uint8_t> tagged_slots() const {
DCHECK(is_valid());
return tagged_slots_;
}
size_t entry_size() const { return bits_end_ - bits_; }
private:
int pc_ = -1;
int deopt_index_ = kNoDeoptIndex;
uint8_t* bits_ = nullptr;
uint8_t* bits_end_ = nullptr;
uint32_t tagged_register_indexes_ = 0;
base::Vector<uint8_t> tagged_slots_;
int trampoline_pc_ = kNoTrampolinePC;
};
@ -102,32 +96,44 @@ class SafepointTable {
SafepointTable(const SafepointTable&) = delete;
SafepointTable& operator=(const SafepointTable&) = delete;
int size() const {
return kHeaderSize + (length_ * (kFixedEntrySize + entry_size_));
}
int length() const { return length_; }
int entry_size() const { return entry_size_; }
int GetPcOffset(int index) const {
DCHECK_GT(length_, index);
return base::Memory<int>(GetPcOffsetLocation(index));
}
int GetTrampolinePcOffset(int index) const {
DCHECK_GT(length_, index);
return base::Memory<int>(GetTrampolineLocation(index));
int byte_size() const {
return kHeaderSize + length_ * (entry_size() + tagged_slots_bytes());
}
int find_return_pc(int pc_offset);
SafepointEntry GetEntry(int index) const {
DCHECK_GT(length_, index);
int deopt_index = base::Memory<int>(GetEncodedInfoLocation(index));
uint8_t* bits = &base::Memory<uint8_t>(entries() + (index * entry_size_));
int trampoline_pc = has_deopt_
? base::Memory<int>(GetTrampolineLocation(index))
: SafepointEntry::kNoTrampolinePC;
return SafepointEntry(deopt_index, bits, bits + entry_size_, trampoline_pc);
Address entry_ptr =
safepoint_table_address_ + kHeaderSize + index * entry_size();
int pc = base::Memory<int>(entry_ptr);
entry_ptr += kPcSize;
int deopt_index = SafepointEntry::kNoDeoptIndex;
int trampoline_pc = SafepointEntry::kNoTrampolinePC;
if (has_deopt_data()) {
deopt_index = base::Memory<int>(entry_ptr);
trampoline_pc = base::Memory<int>(entry_ptr + kIntSize);
entry_ptr += kDeoptDataSize;
}
int tagged_register_indexes = 0;
if (has_register_indexes()) {
tagged_register_indexes = base::Memory<int>(entry_ptr);
entry_ptr += kRegisterIndexesSize;
}
// Entry bits start after the the vector of entries (thus the pc offset of
// the non-existing entry after the last one).
uint8_t* tagged_slots_start = reinterpret_cast<uint8_t*>(
safepoint_table_address_ + kHeaderSize + length_ * entry_size());
base::Vector<uint8_t> tagged_slots(
tagged_slots_start + index * tagged_slots_bytes(),
tagged_slots_bytes());
return SafepointEntry(pc, deopt_index, tagged_register_indexes,
tagged_slots, trampoline_pc);
}
// Returns the entry for the given pc.
@ -136,54 +142,46 @@ class SafepointTable {
void PrintEntry(int index, std::ostream& os) const;
private:
SafepointTable(Address instruction_start, Address safepoint_table_address,
bool has_deopt);
static const uint8_t kNoRegisters = 0xFF;
// Layout information.
static const int kLengthOffset = 0;
static const int kEntrySizeOffset = kLengthOffset + kIntSize;
static const int kHeaderSize = kEntrySizeOffset + kIntSize;
static const int kPcOffset = 0;
static const int kEncodedInfoOffset = kPcOffset + kIntSize;
static const int kTrampolinePcOffset = kEncodedInfoOffset + kIntSize;
static const int kFixedEntrySize = kTrampolinePcOffset + kIntSize;
static constexpr int kLengthOffset = 0;
static constexpr int kEntryConfigurationOffset = kLengthOffset + kIntSize;
static constexpr int kHeaderSize = kEntryConfigurationOffset + kUInt32Size;
static int ReadLength(Address table) {
return base::Memory<int>(table + kLengthOffset);
}
static int ReadEntrySize(Address table) {
return base::Memory<int>(table + kEntrySizeOffset);
}
Address pc_and_deoptimization_indexes() const {
return safepoint_table_address_ + kHeaderSize;
}
Address entries() const {
return safepoint_table_address_ + kHeaderSize + (length_ * kFixedEntrySize);
// An entry consists of the pc, plus optional deopt data (deopt index and
// trampoline PC), plus optional register indexes.
static constexpr int kPcSize = kIntSize;
static constexpr int kDeoptDataSize = 2 * kIntSize;
static constexpr int kRegisterIndexesSize = kIntSize;
using TaggedSlotsBytesField = base::BitField<int, 0, 30>;
using HasDeoptDataField = TaggedSlotsBytesField::Next<bool, 1>;
using HasRegisterIndexesField = HasDeoptDataField::Next<bool, 1>;
SafepointTable(Address instruction_start, Address safepoint_table_address);
int entry_size() const {
return kPcSize + (has_deopt_data() ? kDeoptDataSize : 0) +
(has_register_indexes() ? kRegisterIndexesSize : 0);
}
Address GetPcOffsetLocation(int index) const {
return pc_and_deoptimization_indexes() + (index * kFixedEntrySize);
int tagged_slots_bytes() const {
return TaggedSlotsBytesField::decode(entry_configuration_);
}
Address GetEncodedInfoLocation(int index) const {
return GetPcOffsetLocation(index) + kEncodedInfoOffset;
bool has_deopt_data() const {
return HasDeoptDataField::decode(entry_configuration_);
}
Address GetTrampolineLocation(int index) const {
return GetPcOffsetLocation(index) + kTrampolinePcOffset;
bool has_register_indexes() const {
return HasRegisterIndexesField::decode(entry_configuration_);
}
DISALLOW_GARBAGE_COLLECTION(no_gc_)
const Address instruction_start_;
const bool has_deopt_;
// Safepoint table layout.
const Address safepoint_table_address_;
const int length_;
const int entry_size_;
const uint32_t entry_configuration_;
friend class SafepointTableBuilder;
friend class SafepointEntry;
@ -265,7 +263,7 @@ class SafepointTableBuilder {
int offset_ = -1;
Zone* zone_;
Zone* const zone_;
};
} // namespace internal

View File

@ -995,8 +995,8 @@ void CommonFrame::IterateCompiledFrame(RootVisitor* v) const {
entry->code.GetSafepointEntry(isolate(), inner_pointer);
DCHECK(entry->safepoint_entry.is_valid());
} else {
DCHECK(entry->safepoint_entry.Equals(
entry->code.GetSafepointEntry(isolate(), inner_pointer)));
DCHECK_EQ(entry->safepoint_entry,
entry->code.GetSafepointEntry(isolate(), inner_pointer));
}
code = entry->code;
@ -1090,10 +1090,10 @@ void CommonFrame::IterateCompiledFrame(RootVisitor* v) const {
// Visit pointer spill slots and locals.
DCHECK_GE((stack_slots + kBitsPerByte) / kBitsPerByte,
safepoint_entry.entry_size());
safepoint_entry.tagged_slots().size());
int slot_offset = 0;
PtrComprCageBase cage_base(isolate());
for (uint8_t bits : safepoint_entry.iterate_bits()) {
for (uint8_t bits : safepoint_entry.tagged_slots()) {
while (bits) {
int bit = base::bits::CountTrailingZeros(bits);
bits &= ~(1 << bit);
@ -2042,12 +2042,11 @@ void WasmDebugBreakFrame::Iterate(RootVisitor* v) const {
DCHECK(code);
SafepointTable table(code);
SafepointEntry safepoint_entry = table.FindEntry(caller_pc());
if (!safepoint_entry.has_register_bits()) return;
uint32_t register_bits = safepoint_entry.register_bits();
uint32_t tagged_register_indexes = safepoint_entry.tagged_register_indexes();
while (register_bits != 0) {
int reg_code = base::bits::CountTrailingZeros(register_bits);
register_bits &= ~(1 << reg_code);
while (tagged_register_indexes != 0) {
int reg_code = base::bits::CountTrailingZeros(tagged_register_indexes);
tagged_register_indexes &= ~(1 << reg_code);
FullObjectSlot spill_slot(&Memory<Address>(
fp() +
WasmDebugBreakFrameConstants::GetPushedGpRegisterOffset(reg_code)));

View File

@ -585,18 +585,16 @@ void Code::Disassemble(const char* name, std::ostream& os, Isolate* isolate,
if (has_safepoint_info()) {
SafepointTable table(isolate, current_pc, *this);
os << "Safepoints (size = " << table.size() << ")\n";
os << "Safepoints (entries = " << table.length()
<< ", byte size = " << table.byte_size() << ")\n";
for (int i = 0; i < table.length(); i++) {
int pc_offset = table.GetPcOffset(i);
os << reinterpret_cast<const void*>(InstructionStart() + pc_offset)
<< " ";
os << std::setw(6) << std::hex << pc_offset << " " << std::setw(4);
int trampoline_pc = table.GetTrampolinePcOffset(i);
print_pc(os, trampoline_pc);
SafepointEntry entry = table.GetEntry(i);
os << reinterpret_cast<const void*>(InstructionStart() + entry.pc())
<< " " << std::setw(6) << std::hex << entry.pc() << " ";
print_pc(os, entry.trampoline_pc());
os << std::dec << " ";
table.PrintEntry(i, os);
os << " (sp -> fp) ";
SafepointEntry entry = table.GetEntry(i);
if (entry.has_deoptimization_index()) {
os << std::setw(6) << entry.deoptimization_index();
} else {
@ -610,8 +608,9 @@ void Code::Disassemble(const char* name, std::ostream& os, Isolate* isolate,
if (has_handler_table()) {
HandlerTable table(*this);
os << "Handler Table (size = " << table.NumberOfReturnEntries() << ")\n";
if (CodeKindIsOptimizedJSFunction(kind()))
if (CodeKindIsOptimizedJSFunction(kind())) {
table.HandlerTableReturnPrint(os);
}
os << "\n";
}

View File

@ -436,20 +436,21 @@ void WasmCode::Disassemble(const char* name, std::ostream& os,
if (safepoint_table_offset_ > 0) {
SafepointTable table(this);
os << "Safepoints (size = " << table.size() << ")\n";
// TODO(clemensb): Unify with printing in code.cc.
os << "Safepoints (entries = " << table.length()
<< ", byte size = " << table.byte_size() << ")\n";
for (int i = 0; i < table.length(); i++) {
int pc_offset = table.GetPcOffset(i);
os << reinterpret_cast<const void*>(instruction_start() + pc_offset);
os << std::setw(6) << std::hex << pc_offset << " " << std::dec;
SafepointEntry entry = table.GetEntry(i);
os << reinterpret_cast<const void*>(instruction_start() + entry.pc())
<< " " << std::setw(6) << std::hex << entry.pc() << " ";
table.PrintEntry(i, os);
os << " (sp -> fp)";
SafepointEntry entry = table.GetEntry(i);
if (entry.trampoline_pc() != SafepointEntry::kNoTrampolinePC) {
os << " trampoline: " << std::hex << entry.trampoline_pc() << std::dec;
}
if (entry.has_register_bits()) {
if (entry.tagged_register_indexes() != 0) {
os << " registers: ";
uint32_t register_bits = entry.register_bits();
uint32_t register_bits = entry.tagged_register_indexes();
int bits = 32 - base::bits::CountLeadingZeros32(register_bits);
for (int j = bits - 1; j >= 0; --j) {
os << ((register_bits >> j) & 1);