From 0580829fb5ddc32248ca894b0163a725beb186e4 Mon Sep 17 00:00:00 2001 From: Clemens Backes Date: Fri, 19 Nov 2021 10:15:16 +0100 Subject: [PATCH] [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 Commit-Queue: Clemens Backes Cr-Commit-Position: refs/heads/main@{#78021} --- src/codegen/safepoint-table.cc | 111 ++++++++++----------- src/codegen/safepoint-table.h | 174 ++++++++++++++++----------------- src/execution/frames.cc | 17 ++-- src/objects/code.cc | 17 ++-- src/wasm/wasm-code-manager.cc | 15 +-- 5 files changed, 163 insertions(+), 171 deletions(-) diff --git a/src/codegen/safepoint-table.cc b/src/codegen/safepoint-table.cc index 1a011001b2..726fd70429 100644 --- a/src/codegen/safepoint-table.cc +++ b/src/codegen/safepoint-table.cc @@ -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(safepoint_table_address + kLengthOffset)), + entry_configuration_(base::Memory(safepoint_table_address + + kEntryConfigurationOffset)) {} int SafepointTable::find_return_pc(int pc_offset) { for (int i = 0; i < length(); i++) { - if (GetTrampolinePcOffset(i) == static_cast(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(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(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 bits(bytes_per_entry, 0, zone_); + // Emit bitmaps of tagged stack slots. + ZoneVector bits(tagged_slots_bytes, 0, zone_); for (const EntryBuilder& entry : entries_) { - ZoneChunkList* 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; diff --git a/src/codegen/safepoint-table.h b/src/codegen/safepoint-table.h index b28e052bb5..cfbbfb0b53 100644 --- a/src/codegen/safepoint-table.h +++ b/src/codegen/safepoint-table.h @@ -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 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 iterate_bits() const { - return base::make_iterator_range(bits_, bits_end_); + base::Vector 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 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(GetPcOffsetLocation(index)); - } - - int GetTrampolinePcOffset(int index) const { - DCHECK_GT(length_, index); - return base::Memory(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(GetEncodedInfoLocation(index)); - uint8_t* bits = &base::Memory(entries() + (index * entry_size_)); - int trampoline_pc = has_deopt_ - ? base::Memory(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(entry_ptr); + entry_ptr += kPcSize; + int deopt_index = SafepointEntry::kNoDeoptIndex; + int trampoline_pc = SafepointEntry::kNoTrampolinePC; + if (has_deopt_data()) { + deopt_index = base::Memory(entry_ptr); + trampoline_pc = base::Memory(entry_ptr + kIntSize); + entry_ptr += kDeoptDataSize; + } + int tagged_register_indexes = 0; + if (has_register_indexes()) { + tagged_register_indexes = base::Memory(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( + safepoint_table_address_ + kHeaderSize + length_ * entry_size()); + base::Vector 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(table + kLengthOffset); - } - static int ReadEntrySize(Address table) { - return base::Memory(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; + using HasDeoptDataField = TaggedSlotsBytesField::Next; + using HasRegisterIndexesField = HasDeoptDataField::Next; + + 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 diff --git a/src/execution/frames.cc b/src/execution/frames.cc index 52d852e731..931c9bfa04 100644 --- a/src/execution/frames.cc +++ b/src/execution/frames.cc @@ -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
( fp() + WasmDebugBreakFrameConstants::GetPushedGpRegisterOffset(reg_code))); diff --git a/src/objects/code.cc b/src/objects/code.cc index 04e2e8bc95..53480a77a5 100644 --- a/src/objects/code.cc +++ b/src/objects/code.cc @@ -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(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(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"; } diff --git a/src/wasm/wasm-code-manager.cc b/src/wasm/wasm-code-manager.cc index 679e084fec..110e8b8002 100644 --- a/src/wasm/wasm-code-manager.cc +++ b/src/wasm/wasm-code-manager.cc @@ -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(instruction_start() + pc_offset); - os << std::setw(6) << std::hex << pc_offset << " " << std::dec; + SafepointEntry entry = table.GetEntry(i); + os << reinterpret_cast(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);