[maglev] Add safepoints for deferred calls

Add a concept of "register snapshots" which snapshot the end-state
of the register allocation for a node (i.e. the state of the register
allocation when the node's code completes). These can be requested by
nodes, so that they know which registers need to be kept alive by the
node, and which of those are tagged.

Nodes can then use this information to temporarily spill registers
across a deferred call, without requiring the register allocator to
spill them unconditionally on the non-deferred path. The maglev
safepoint table has support for these additional spilled registers.

Bug: v8:7700
Change-Id: Id0052b5da86dd263f9019b1433fe5994a472a5b1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3751203
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81614}
This commit is contained in:
Leszek Swirski 2022-07-08 17:18:04 +02:00 committed by V8 LUCI CQ
parent 0ed101e015
commit 8103fe573a
4 changed files with 192 additions and 70 deletions

View File

@ -9,6 +9,7 @@
#include "src/codegen/interface-descriptors-inl.h"
#include "src/codegen/interface-descriptors.h"
#include "src/codegen/macro-assembler-inl.h"
#include "src/codegen/maglev-safepoint-table.h"
#include "src/codegen/register.h"
#include "src/codegen/reglist.h"
#include "src/codegen/x64/assembler-x64.h"
@ -121,6 +122,38 @@ void PushInput(MaglevCodeGenState* code_gen_state, const Input& input) {
}
}
class SaveRegisterStateForCall {
public:
SaveRegisterStateForCall(MaglevCodeGenState* code_gen_state,
RegisterSnapshot snapshot)
: code_gen_state(code_gen_state), snapshot_(snapshot) {
__ PushAll(snapshot_.live_registers);
__ PushAll(snapshot_.live_double_registers);
}
~SaveRegisterStateForCall() {
__ PopAll(snapshot_.live_double_registers);
__ PopAll(snapshot_.live_registers);
}
MaglevSafepointTableBuilder::Safepoint DefineSafepoint() {
auto safepoint = code_gen_state->safepoint_table_builder()->DefineSafepoint(
code_gen_state->masm());
int pushed_reg_index = 0;
for (Register reg : snapshot_.live_registers) {
if (snapshot_.live_tagged_registers.has(reg)) {
safepoint.DefineTaggedRegister(pushed_reg_index);
}
pushed_reg_index++;
}
return safepoint;
}
private:
MaglevCodeGenState* code_gen_state;
RegisterSnapshot snapshot_;
};
// ---
// Deferred code handling.
// ---
@ -680,18 +713,35 @@ void CheckMapsWithMigration::GenerateCode(MaglevCodeGenState* code_gen_state,
Immediate(Map::Bits3::IsDeprecatedBit::kMask));
__ j(zero, &deopt_info->deopt_entry_label);
// Otherwise, try migrating the object. If the migration returns Smi
// zero, then it failed and we should deopt.
__ Push(object);
__ Move(kContextRegister,
code_gen_state->broker()->target_native_context().object());
// TODO(verwaest): We're calling so we need to spill around it.
__ CallRuntime(Runtime::kTryMigrateInstance);
__ cmpl(kReturnRegister0, Immediate(0));
// Otherwise, try migrating the object. If the migration
// returns Smi zero, then it failed and we should deopt.
Register return_val = Register::no_reg();
{
SaveRegisterStateForCall save_register_state(
code_gen_state, node->register_snapshot());
__ Push(object);
__ Move(kContextRegister,
code_gen_state->broker()->target_native_context().object());
__ CallRuntime(Runtime::kTryMigrateInstance);
save_register_state.DefineSafepoint();
// Make sure the return value is preserved across the live register
// restoring pop all.
return_val = kReturnRegister0;
if (node->register_snapshot().live_registers.has(return_val)) {
DCHECK(!node->register_snapshot().live_registers.has(map_tmp));
__ Move(map_tmp, return_val);
return_val = map_tmp;
}
}
// On failure, the returned value is zero
__ cmpl(return_val, Immediate(0));
__ j(equal, &deopt_info->deopt_entry_label);
// The migrated object is returned on success, retry the map check.
__ Move(object, kReturnRegister0);
__ Move(object, return_val);
__ LoadMap(map_tmp, object);
__ Cmp(map_tmp, node->map().object());
__ j(equal, return_label);
@ -1575,16 +1625,19 @@ void ReduceInterruptBudget::GenerateCode(MaglevCodeGenState* code_gen_state,
Immediate(amount()));
JumpToDeferredIf(
less, code_gen_state,
[](MaglevCodeGenState* code_gen_state, Label* return_label) {
// TODO(leszeks): Only save registers if they're not free (requires
// fixing the regalloc, same as for scratch).
__ PushCallerSaved(SaveFPRegsMode::kSave);
__ Move(kContextRegister, code_gen_state->native_context().object());
__ Push(MemOperand(rbp, StandardFrameConstants::kFunctionOffset));
__ CallRuntime(Runtime::kBytecodeBudgetInterruptWithStackCheck, 1);
__ PopCallerSaved(SaveFPRegsMode::kSave);
[](MaglevCodeGenState* code_gen_state, Label* return_label,
ReduceInterruptBudget* node) {
{
SaveRegisterStateForCall save_register_state(
code_gen_state, node->register_snapshot());
__ Move(kContextRegister, code_gen_state->native_context().object());
__ Push(MemOperand(rbp, StandardFrameConstants::kFunctionOffset));
__ CallRuntime(Runtime::kBytecodeBudgetInterruptWithStackCheck, 1);
save_register_state.DefineSafepoint();
}
__ jmp(return_label);
});
},
this);
}
void ReduceInterruptBudget::PrintParams(
std::ostream& os, MaglevGraphLabeller* graph_labeller) const {

View File

@ -280,6 +280,9 @@ class OpProperties {
constexpr bool is_conversion() const {
return kIsConversionBit::decode(bitfield_);
}
constexpr bool needs_register_snapshot() const {
return kNeedsRegisterSnapshotBit::decode(bitfield_);
}
constexpr bool is_pure() const {
return (bitfield_ | kPureMask) == kPureValue;
}
@ -325,12 +328,21 @@ class OpProperties {
static constexpr OpProperties ConversionNode() {
return OpProperties(kIsConversionBit::encode(true));
}
static constexpr OpProperties NeedsRegisterSnapshot() {
return OpProperties(kNeedsRegisterSnapshotBit::encode(true));
}
static constexpr OpProperties JSCall() {
return Call() | NonMemorySideEffects() | LazyDeopt();
}
static constexpr OpProperties AnySideEffects() {
return Reading() | Writing() | NonMemorySideEffects();
}
static constexpr OpProperties DeferredCall() {
// Operations with a deferred call need a snapshot of register state,
// because they need to be able to push registers to save them, and annotate
// the safepoint with information about which registers are tagged.
return NeedsRegisterSnapshot();
}
constexpr explicit OpProperties(uint32_t bitfield) : bitfield_(bitfield) {}
operator uint32_t() const { return bitfield_; }
@ -345,6 +357,7 @@ class OpProperties {
using kValueRepresentationBits =
kNonMemorySideEffectsBit::Next<ValueRepresentation, 2>;
using kIsConversionBit = kValueRepresentationBits::Next<bool, 1>;
using kNeedsRegisterSnapshotBit = kIsConversionBit::Next<bool, 1>;
static const uint32_t kPureMask = kCanReadBit::kMask | kCanWriteBit::kMask |
kNonMemorySideEffectsBit::kMask;
@ -355,7 +368,7 @@ class OpProperties {
const uint32_t bitfield_;
public:
static const size_t kSize = kIsConversionBit::kLastUsedBit + 1;
static const size_t kSize = kNeedsRegisterSnapshotBit::kLastUsedBit + 1;
};
class ValueLocation {
@ -451,6 +464,12 @@ class DeoptInfo {
int translation_index = -1;
};
struct RegisterSnapshot {
RegList live_registers;
RegList live_tagged_registers;
DoubleRegList live_double_registers;
};
class EagerDeoptInfo : public DeoptInfo {
public:
EagerDeoptInfo(Zone* zone, const MaglevCompilationUnit& compilation_unit,
@ -487,6 +506,20 @@ struct opcode_of_helper;
NODE_BASE_LIST(DEF_OPCODE_OF)
#undef DEF_OPCODE_OF
template <typename T>
T* ObjectPtrBeforeAddress(void* address) {
char* address_as_char_ptr = reinterpret_cast<char*>(address);
char* object_ptr_as_char_ptr = address_as_char_ptr - sizeof(T);
return reinterpret_cast<T*>(object_ptr_as_char_ptr);
}
template <typename T>
const T* ObjectPtrBeforeAddress(const void* address) {
const char* address_as_char_ptr = reinterpret_cast<const char*>(address);
const char* object_ptr_as_char_ptr = address_as_char_ptr - sizeof(T);
return reinterpret_cast<const T*>(object_ptr_as_char_ptr);
}
} // namespace detail
class NodeBase : public ZoneObject {
@ -530,11 +563,11 @@ class NodeBase : public ZoneObject {
CheckpointedInterpreterState checkpoint, Args&&... args) {
Derived* node = New<Derived>(zone, std::forward<Args>(args)...);
if constexpr (Derived::kProperties.can_eager_deopt()) {
new (node->eager_deopt_info_address())
new (node->eager_deopt_info())
EagerDeoptInfo(zone, compilation_unit, checkpoint);
} else {
static_assert(Derived::kProperties.can_lazy_deopt());
new (node->lazy_deopt_info_address())
new (node->lazy_deopt_info())
LazyDeoptInfo(zone, compilation_unit, checkpoint);
}
return node;
@ -581,16 +614,20 @@ class NodeBase : public ZoneObject {
return static_cast<int>(InputCountField::decode(bitfield_));
}
Input& input(int index) { return *input_address(index); }
const Input& input(int index) const { return *input_address(index); }
Input& input(int index) {
DCHECK_LT(index, input_count());
return *(input_base() - index);
}
const Input& input(int index) const {
DCHECK_LT(index, input_count());
return *(input_base() - index);
}
// Input iterators, use like:
//
// for (Input& input : *node) { ... }
auto begin() { return std::make_reverse_iterator(input_address(-1)); }
auto end() {
return std::make_reverse_iterator(input_address(input_count() - 1));
}
auto begin() { return std::make_reverse_iterator(&input(-1)); }
auto end() { return std::make_reverse_iterator(&input(input_count() - 1)); }
constexpr bool has_id() const { return id_ != kInvalidNodeId; }
constexpr NodeIdT id() const {
@ -616,49 +653,65 @@ class NodeBase : public ZoneObject {
EagerDeoptInfo* eager_deopt_info() {
DCHECK(properties().can_eager_deopt());
DCHECK(!properties().can_lazy_deopt());
return (
reinterpret_cast<EagerDeoptInfo*>(input_address(input_count() - 1)) -
1);
return detail::ObjectPtrBeforeAddress<EagerDeoptInfo>(last_input_address());
}
const EagerDeoptInfo* eager_deopt_info() const {
DCHECK(properties().can_eager_deopt());
DCHECK(!properties().can_lazy_deopt());
return (reinterpret_cast<const EagerDeoptInfo*>(
input_address(input_count() - 1)) -
1);
return detail::ObjectPtrBeforeAddress<EagerDeoptInfo>(last_input_address());
}
LazyDeoptInfo* lazy_deopt_info() {
DCHECK(properties().can_lazy_deopt());
DCHECK(!properties().can_eager_deopt());
return (reinterpret_cast<LazyDeoptInfo*>(input_address(input_count() - 1)) -
1);
return detail::ObjectPtrBeforeAddress<LazyDeoptInfo>(last_input_address());
}
const LazyDeoptInfo* lazy_deopt_info() const {
DCHECK(properties().can_lazy_deopt());
DCHECK(!properties().can_eager_deopt());
return (reinterpret_cast<const LazyDeoptInfo*>(
input_address(input_count() - 1)) -
1);
return detail::ObjectPtrBeforeAddress<LazyDeoptInfo>(last_input_address());
}
const RegisterSnapshot& register_snapshot() const {
DCHECK(properties().needs_register_snapshot());
DCHECK(!properties().can_lazy_deopt());
if (properties().can_eager_deopt()) {
return *detail::ObjectPtrBeforeAddress<RegisterSnapshot>(
eager_deopt_info());
} else {
return *detail::ObjectPtrBeforeAddress<RegisterSnapshot>(
last_input_address());
}
}
void set_register_snapshot(RegisterSnapshot snapshot) {
DCHECK(properties().needs_register_snapshot());
DCHECK(!properties().can_lazy_deopt());
if (properties().can_eager_deopt()) {
*detail::ObjectPtrBeforeAddress<RegisterSnapshot>(eager_deopt_info()) =
snapshot;
} else {
*detail::ObjectPtrBeforeAddress<RegisterSnapshot>(last_input_address()) =
snapshot;
}
}
protected:
explicit NodeBase(uint64_t bitfield) : bitfield_(bitfield) {}
Input* input_address(int index) {
DCHECK_LT(index, input_count());
return reinterpret_cast<Input*>(this) - (index + 1);
Input* input_base() { return detail::ObjectPtrBeforeAddress<Input>(this); }
const Input* input_base() const {
return detail::ObjectPtrBeforeAddress<Input>(this);
}
Input* last_input_address() { return &input(input_count() - 1); }
const Input* last_input_address() const { return &input(input_count() - 1); }
const Input* input_address(int index) const {
DCHECK_LT(index, input_count());
return reinterpret_cast<const Input*>(this) - (index + 1);
}
void set_input(int index, ValueNode* input) {
new (input_address(index)) Input(input);
void set_input(int index, ValueNode* node) {
new (&input(index)) Input(node);
}
// For nodes that don't have data past the input, allow trimming the input
@ -684,20 +737,6 @@ class NodeBase : public ZoneObject {
// entry into this node.
void RequireSpecificTemporary(Register reg) { temporaries_.set(reg); }
EagerDeoptInfo* eager_deopt_info_address() {
DCHECK(properties().can_eager_deopt());
DCHECK(!properties().can_lazy_deopt());
return reinterpret_cast<EagerDeoptInfo*>(input_address(input_count() - 1)) -
1;
}
LazyDeoptInfo* lazy_deopt_info_address() {
DCHECK(!properties().can_eager_deopt());
DCHECK(properties().can_lazy_deopt());
return reinterpret_cast<LazyDeoptInfo*>(input_address(input_count() - 1)) -
1;
}
private:
template <class Derived, typename... Args>
static Derived* Allocate(Zone* zone, size_t input_count, Args&&... args) {
@ -708,10 +747,18 @@ class NodeBase : public ZoneObject {
"that we cannot have both lazy and eager deopts on a node. If we ever "
"need this, we have to update accessors to check node->properties() "
"for which deopts are active.");
const size_t size_before_node =
input_count * sizeof(Input) +
constexpr size_t size_before_inputs = RoundUp<alignof(Input)>(
(Derived::kProperties.needs_register_snapshot()
? sizeof(RegisterSnapshot)
: 0) +
(Derived::kProperties.can_eager_deopt() ? sizeof(EagerDeoptInfo) : 0) +
(Derived::kProperties.can_lazy_deopt() ? sizeof(LazyDeoptInfo) : 0);
(Derived::kProperties.can_lazy_deopt() ? sizeof(LazyDeoptInfo) : 0));
static_assert(IsAligned(size_before_inputs, alignof(Input)));
const size_t size_before_node =
size_before_inputs + input_count * sizeof(Input);
DCHECK(IsAligned(size_before_inputs, alignof(Derived)));
const size_t size = size_before_node + sizeof(Derived);
intptr_t raw_buffer =
reinterpret_cast<intptr_t>(zone->Allocate<NodeWithInlineInputs>(size));
@ -1019,7 +1066,7 @@ class FixedInputNodeT : public NodeT<Derived> {
constexpr bool has_inputs() const { return input_count() > 0; }
constexpr uint16_t input_count() const { return kInputCount; }
auto end() {
return std::make_reverse_iterator(this->input_address(input_count() - 1));
return std::make_reverse_iterator(&this->input(input_count() - 1));
}
protected:
@ -1051,7 +1098,7 @@ class FixedInputValueNodeT : public ValueNodeT<Derived> {
constexpr bool has_inputs() const { return input_count() > 0; }
constexpr uint16_t input_count() const { return kInputCount; }
auto end() {
return std::make_reverse_iterator(this->input_address(input_count() - 1));
return std::make_reverse_iterator(&this->input(input_count() - 1));
}
protected:
@ -1731,7 +1778,7 @@ class CheckMapsWithMigration
// mark that to generate stack maps. Mark as call so we at least clear the
// registers since we currently don't properly spill either.
static constexpr OpProperties kProperties =
OpProperties::EagerDeopt() | OpProperties::Call();
OpProperties::EagerDeopt() | OpProperties::DeferredCall();
compiler::MapRef map() const { return map_; }
@ -2147,6 +2194,8 @@ class ReduceInterruptBudget : public FixedInputNodeT<0, ReduceInterruptBudget> {
DCHECK_GT(amount, 0);
}
static constexpr OpProperties kProperties = OpProperties::DeferredCall();
int amount() const { return amount_; }
void AllocateVreg(MaglevVregAllocationState*);
@ -2304,7 +2353,7 @@ class UnconditionalControlNodeT : public UnconditionalControlNode {
constexpr bool has_inputs() const { return input_count() > 0; }
constexpr uint16_t input_count() const { return kInputCount; }
auto end() {
return std::make_reverse_iterator(input_address(input_count() - 1));
return std::make_reverse_iterator(&this->input(input_count() - 1));
}
protected:
@ -2348,7 +2397,7 @@ class ConditionalControlNodeT : public ConditionalControlNode {
constexpr bool has_inputs() const { return input_count() > 0; }
constexpr uint16_t input_count() const { return kInputCount; }
auto end() {
return std::make_reverse_iterator(input_address(input_count() - 1));
return std::make_reverse_iterator(&this->input(input_count() - 1));
}
protected:

View File

@ -485,6 +485,8 @@ void StraightForwardRegisterAllocator::AllocateNode(Node* node) {
UpdateUse(*node->lazy_deopt_info());
}
if (node->properties().needs_register_snapshot()) SaveRegisterSnapshot(node);
if (FLAG_trace_maglev_regalloc) {
printing_visitor_->Process(node,
ProcessingState(compilation_info_, block_it_));
@ -716,6 +718,7 @@ void StraightForwardRegisterAllocator::AllocateControlNode(ControlNode* node,
DCHECK_EQ(node->input_count(), 0);
DCHECK(!node->properties().can_eager_deopt());
DCHECK(!node->properties().can_lazy_deopt());
DCHECK(!node->properties().needs_register_snapshot());
// Initialize phis before assigning inputs, in case one of the inputs
// conflicts with a fixed phi.
@ -748,6 +751,8 @@ void StraightForwardRegisterAllocator::AllocateControlNode(ControlNode* node,
if (node->properties().is_call()) SpillAndClearRegisters();
DCHECK(!node->properties().needs_register_snapshot());
DCHECK_EQ(general_registers_.free() | node->temporaries(),
general_registers_.free());
@ -1076,6 +1081,19 @@ void StraightForwardRegisterAllocator::SpillAndClearRegisters() {
SpillAndClearRegisters(double_registers_);
}
void StraightForwardRegisterAllocator::SaveRegisterSnapshot(NodeBase* node) {
RegisterSnapshot snapshot;
general_registers_.ForEachUsedRegister([&](Register reg, ValueNode* node) {
if (node->properties().value_representation() ==
ValueRepresentation::kTagged) {
snapshot.live_tagged_registers.set(reg);
}
});
snapshot.live_registers = general_registers_.used();
snapshot.live_double_registers = double_registers_.used();
node->set_register_snapshot(snapshot);
}
void StraightForwardRegisterAllocator::AllocateSpillSlot(ValueNode* node) {
DCHECK(!node->is_loadable());
uint32_t free_slot;

View File

@ -163,6 +163,8 @@ class StraightForwardRegisterAllocator {
void SpillAndClearRegisters(RegisterFrameState<RegisterT>& registers);
void SpillAndClearRegisters();
void SaveRegisterSnapshot(NodeBase* node);
void FreeRegistersUsedBy(ValueNode* node);
template <typename RegisterT>
RegisterT FreeUnblockedRegister();