blob: 49848d56ae04e8b4c7ad71a6cd42c1e6c7c5b932 [file] [log] [blame]
// Copyright 2011 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_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"
#include "src/utils/allocation.h"
#include "src/utils/bit-vector.h"
#include "src/utils/utils.h"
#include "src/zone/zone-chunk-list.h"
#include "src/zone/zone.h"
namespace v8 {
namespace internal {
namespace wasm {
class WasmCode;
} // namespace wasm
class SafepointEntry {
public:
static constexpr int kNoDeoptIndex = -1;
static constexpr int kNoTrampolinePC = -1;
SafepointEntry() = default;
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_initialized());
}
bool is_initialized() const { return tagged_slots_.begin() != nullptr; }
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() {
*this = SafepointEntry{};
DCHECK(!is_initialized());
}
int pc() const { return pc_; }
int trampoline_pc() const { return trampoline_pc_; }
bool has_deoptimization_index() const {
DCHECK(is_initialized());
return deopt_index_ != kNoDeoptIndex;
}
int deoptimization_index() const {
DCHECK(is_initialized() && has_deoptimization_index());
return deopt_index_;
}
uint32_t tagged_register_indexes() const {
DCHECK(is_initialized());
return tagged_register_indexes_;
}
base::Vector<const uint8_t> tagged_slots() const {
DCHECK(is_initialized());
return tagged_slots_;
}
private:
int pc_ = -1;
int deopt_index_ = kNoDeoptIndex;
uint32_t tagged_register_indexes_ = 0;
base::Vector<uint8_t> tagged_slots_;
int trampoline_pc_ = kNoTrampolinePC;
};
// A wrapper class for accessing the safepoint table embedded into the Code
// object.
class SafepointTable {
public:
// The isolate and pc arguments are used for figuring out whether pc
// belongs to the embedded or un-embedded code blob.
explicit SafepointTable(Isolate* isolate, Address pc, Code code);
#if V8_ENABLE_WEBASSEMBLY
explicit SafepointTable(const wasm::WasmCode* code);
#endif // V8_ENABLE_WEBASSEMBLY
SafepointTable(const SafepointTable&) = delete;
SafepointTable& operator=(const SafepointTable&) = delete;
int length() const { return length_; }
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);
Address entry_ptr =
safepoint_table_address_ + kHeaderSize + index * entry_size();
int pc = read_bytes(&entry_ptr, pc_size());
int deopt_index = SafepointEntry::kNoDeoptIndex;
int trampoline_pc = SafepointEntry::kNoTrampolinePC;
if (has_deopt_data()) {
STATIC_ASSERT(SafepointEntry::kNoDeoptIndex == -1);
STATIC_ASSERT(SafepointEntry::kNoTrampolinePC == -1);
// `-1` to restore the original value, see also
// SafepointTableBuilder::Emit.
deopt_index = read_bytes(&entry_ptr, deopt_index_size()) - 1;
trampoline_pc = read_bytes(&entry_ptr, pc_size()) - 1;
DCHECK(deopt_index >= 0 || deopt_index == SafepointEntry::kNoDeoptIndex);
DCHECK(trampoline_pc >= 0 ||
trampoline_pc == SafepointEntry::kNoTrampolinePC);
}
int tagged_register_indexes =
read_bytes(&entry_ptr, register_indexes_size());
// 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.
SafepointEntry FindEntry(Address pc) const;
void Print(std::ostream&) const;
private:
// Layout information.
static constexpr int kLengthOffset = 0;
static constexpr int kEntryConfigurationOffset = kLengthOffset + kIntSize;
static constexpr int kHeaderSize = kEntryConfigurationOffset + kUInt32Size;
using HasDeoptDataField = base::BitField<bool, 0, 1>;
using RegisterIndexesSizeField = HasDeoptDataField::Next<int, 3>;
using PcSizeField = RegisterIndexesSizeField::Next<int, 3>;
using DeoptIndexSizeField = PcSizeField::Next<int, 3>;
// In 22 bits, we can encode up to 4M bytes, corresponding to 32M frame slots,
// which is 128MB on 32-bit and 256MB on 64-bit systems. The stack size is
// limited to a bit below 1MB anyway (see FLAG_stack_size).
using TaggedSlotsBytesField = DeoptIndexSizeField::Next<int, 22>;
SafepointTable(Address instruction_start, Address safepoint_table_address);
int entry_size() const {
int deopt_data_size = has_deopt_data() ? pc_size() + deopt_index_size() : 0;
return pc_size() + deopt_data_size + register_indexes_size();
}
int tagged_slots_bytes() const {
return TaggedSlotsBytesField::decode(entry_configuration_);
}
bool has_deopt_data() const {
return HasDeoptDataField::decode(entry_configuration_);
}
int pc_size() const { return PcSizeField::decode(entry_configuration_); }
int deopt_index_size() const {
return DeoptIndexSizeField::decode(entry_configuration_);
}
int register_indexes_size() const {
return RegisterIndexesSizeField::decode(entry_configuration_);
}
static int read_bytes(Address* ptr, int bytes) {
uint32_t result = 0;
for (int b = 0; b < bytes; ++b, ++*ptr) {
result |= uint32_t{*reinterpret_cast<uint8_t*>(*ptr)} << (8 * b);
}
return static_cast<int>(result);
}
DISALLOW_GARBAGE_COLLECTION(no_gc_)
const Address instruction_start_;
// Safepoint table layout.
const Address safepoint_table_address_;
const int length_;
const uint32_t entry_configuration_;
friend class SafepointTableBuilder;
friend class SafepointEntry;
};
class SafepointTableBuilder {
private:
struct EntryBuilder {
int pc;
int deopt_index = SafepointEntry::kNoDeoptIndex;
int trampoline = SafepointEntry::kNoTrampolinePC;
GrowableBitVector* stack_indexes;
uint32_t register_indexes = 0;
EntryBuilder(Zone* zone, int pc)
: pc(pc), stack_indexes(zone->New<GrowableBitVector>()) {}
};
public:
explicit SafepointTableBuilder(Zone* zone) : entries_(zone), zone_(zone) {}
SafepointTableBuilder(const SafepointTableBuilder&) = delete;
SafepointTableBuilder& operator=(const SafepointTableBuilder&) = delete;
bool emitted() const {
return safepoint_table_offset_ != kNoSafepointTableOffset;
}
int safepoint_table_offset() const {
DCHECK(emitted());
return safepoint_table_offset_;
}
class Safepoint {
public:
void DefineTaggedStackSlot(int index) {
// Note it is only valid to specify stack slots here that are *not* in
// the fixed part of the frame (e.g. argc, target, context, stored rbp,
// return address). Frame iteration handles the fixed part of the frame
// with custom code, see CommonFrame::IterateCompiledFrame.
entry_->stack_indexes->Add(index, table_->zone_);
table_->UpdateMinMaxStackIndex(index);
}
void DefineTaggedRegister(int reg_code) {
DCHECK_LT(reg_code,
kBitsPerByte * sizeof(EntryBuilder::register_indexes));
entry_->register_indexes |= 1u << reg_code;
}
private:
friend class SafepointTableBuilder;
Safepoint(EntryBuilder* entry, SafepointTableBuilder* table)
: entry_(entry), table_(table) {}
EntryBuilder* const entry_;
SafepointTableBuilder* const table_;
};
// Define a new safepoint for the current position in the body.
Safepoint DefineSafepoint(Assembler* assembler);
// Emit the safepoint table after the body. The number of bits per
// entry must be enough to hold all the pointer indexes.
V8_EXPORT_PRIVATE void Emit(Assembler* assembler, int bits_per_entry);
// Find the Deoptimization Info with pc offset {pc} and update its
// trampoline field. Calling this function ensures that the safepoint
// table contains the trampoline PC {trampoline} that replaced the
// return PC {pc} on the stack.
int UpdateDeoptimizationInfo(int pc, int trampoline, int start,
int deopt_index);
private:
// Remove consecutive identical entries.
void RemoveDuplicates();
void UpdateMinMaxStackIndex(int index) {
#ifdef DEBUG
max_stack_index_ = std::max(max_stack_index_, index);
#endif // DEBUG
min_stack_index_ = std::min(min_stack_index_, index);
}
int min_stack_index() const {
return min_stack_index_ == std::numeric_limits<int>::max()
? 0
: min_stack_index_;
}
static constexpr int kNoSafepointTableOffset = -1;
// Tracks the min/max stack slot index over all entries. We need the minimum
// index when encoding the actual table since we shift all unused lower
// indices out of the encoding. Tracking the indices during safepoint
// construction means we don't have to iterate again later.
#ifdef DEBUG
int max_stack_index_ = 0;
#endif // DEBUG
int min_stack_index_ = std::numeric_limits<int>::max();
ZoneChunkList<EntryBuilder> entries_;
int safepoint_table_offset_ = kNoSafepointTableOffset;
Zone* const zone_;
};
} // namespace internal
} // namespace v8
#endif // V8_CODEGEN_SAFEPOINT_TABLE_H_