#include "src/maglev/maglev-code-generator.h"
#include <algorithm>
#include "src/base/hashmap.h"
#include "src/base/logging.h"
#include "src/codegen/code-desc.h"
#include "src/codegen/compiler.h"
#include "src/codegen/interface-descriptors-inl.h"
#include "src/codegen/interface-descriptors.h"
#include "src/codegen/register.h"
#include "src/codegen/reglist.h"
#include "src/codegen/safepoint-table.h"
#include "src/codegen/source-position.h"
#include "src/common/globals.h"
#include "src/compiler/backend/instruction.h"
#include "src/compiler/frame-states.h"
#include "src/deoptimizer/deoptimize-reason.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/deoptimizer/frame-translation-builder.h"
#include "src/execution/frame-constants.h"
#include "src/flags/flags.h"
#include "src/handles/global-handles-inl.h"
#include "src/interpreter/bytecode-register.h"
#include "src/maglev/maglev-assembler-inl.h"
#include "src/maglev/maglev-code-gen-state-inl.h"
#include "src/maglev/maglev-compilation-unit.h"
#include "src/maglev/maglev-graph-labeller.h"
#include "src/maglev/maglev-graph-printer.h"
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-graph.h"
#include "src/maglev/maglev-ir-inl.h"
#include "src/maglev/maglev-ir.h"
#include "src/maglev/maglev-regalloc-data.h"
#include "src/maglev/maglev-regalloc-node-info.h"
#include "src/objects/code-inl.h"
#include "src/objects/deoptimization-data.h"
#include "src/utils/identity-map.h"
namespace v8 {
namespace internal {
namespace maglev {
#define __ masm()->
namespace {
template <typename RegisterT>
struct RegisterTHelper;
template <>
struct RegisterTHelper<Register> {
static constexpr RegList kAllocatableRegisters =
MaglevAssembler::GetAllocatableRegisters();
};
template <>
struct RegisterTHelper<DoubleRegister> {
static constexpr DoubleRegList kAllocatableRegisters =
MaglevAssembler::GetAllocatableDoubleRegisters();
};
enum NeedsDecompression { kDoesNotNeedDecompression, kNeedsDecompression };
template <typename RegisterT, bool DecompressIfNeeded>
class ParallelMoveResolver {
static constexpr auto kAllocatableRegistersT =
RegisterTHelper<RegisterT>::kAllocatableRegisters;
static_assert(!DecompressIfNeeded || std::is_same_v<Register, RegisterT>);
static_assert(!DecompressIfNeeded || COMPRESS_POINTERS_BOOL);
public:
explicit ParallelMoveResolver(MaglevAssembler* masm)
: masm_(masm), scratch_(RegisterT::no_reg()) {}
void RecordMove(ValueNode* source_node, compiler::InstructionOperand source,
compiler::AllocatedOperand target,
bool target_needs_to_be_decompressed) {
if (target.IsAnyRegister()) {
RecordMoveToRegister(source_node, source, ToRegisterT<RegisterT>(target),
target_needs_to_be_decompressed);
} else {
RecordMoveToStackSlot(source_node, source,
masm_->GetFramePointerOffsetForStackSlot(target),
target_needs_to_be_decompressed);
}
}
void RecordMove(ValueNode* source_node, compiler::InstructionOperand source,
RegisterT target_reg,
NeedsDecompression target_needs_to_be_decompressed) {
RecordMoveToRegister(source_node, source, target_reg,
target_needs_to_be_decompressed);
}
void EmitMoves(RegisterT scratch) {
DCHECK(!scratch_.is_valid());
scratch_ = scratch;
for (RegisterT reg : kAllocatableRegistersT) {
StartEmitMoveChain(reg);
ValueNode* materializing_register_move =
materializing_register_moves_[reg.code()];
if (materializing_register_move) {
materializing_register_move->LoadToRegister(masm_, reg);
}
}
while (!moves_from_stack_slot_.empty()) {
StartEmitMoveChain(moves_from_stack_slot_.begin()->first);
}
for (auto [stack_slot, node] : materializing_stack_slot_moves_) {
node->LoadToRegister(masm_, scratch_);
__ Move(StackSlot{stack_slot}, scratch_);
}
}
ParallelMoveResolver(ParallelMoveResolver&&) = delete;
ParallelMoveResolver operator=(ParallelMoveResolver&&) = delete;
ParallelMoveResolver(const ParallelMoveResolver&) = delete;
ParallelMoveResolver operator=(const ParallelMoveResolver&) = delete;
private:
struct DummyNeedsDecompression {
DummyNeedsDecompression(NeedsDecompression) {}
};
struct GapMoveTargets {
base::SmallVector<int32_t, 1> stack_slots = base::SmallVector<int32_t, 1>{};
RegListBase<RegisterT> registers;
V8_NO_UNIQUE_ADDRESS
std::conditional_t<DecompressIfNeeded, NeedsDecompression,
DummyNeedsDecompression>
needs_decompression = kDoesNotNeedDecompression;
GapMoveTargets() = default;
GapMoveTargets(GapMoveTargets&&) V8_NOEXCEPT = default;
GapMoveTargets& operator=(GapMoveTargets&&) V8_NOEXCEPT = default;
GapMoveTargets(const GapMoveTargets&) = delete;
GapMoveTargets& operator=(const GapMoveTargets&) = delete;
bool is_empty() const {
return registers.is_empty() && stack_slots.empty();
}
};
#ifdef DEBUG
void CheckNoExistingMoveToRegister(RegisterT target_reg) {
for (RegisterT reg : kAllocatableRegistersT) {
if (moves_from_register_[reg.code()].registers.has(target_reg)) {
FATAL("Existing move from %s to %s", RegisterName(reg),
RegisterName(target_reg));
}
}
for (auto& [stack_slot, targets] : moves_from_stack_slot_) {
if (targets.registers.has(target_reg)) {
FATAL("Existing move from stack slot %d to %s", stack_slot,
RegisterName(target_reg));
}
}
if (materializing_register_moves_[target_reg.code()] != nullptr) {
FATAL("Existing materialization of %p to %s",
materializing_register_moves_[target_reg.code()],
RegisterName(target_reg));
}
}
void CheckNoExistingMoveToStackSlot(int32_t target_slot) {
for (RegisterT reg : kAllocatableRegistersT) {
auto& stack_slots = moves_from_register_[reg.code()].stack_slots;
if (std::any_of(stack_slots.begin(), stack_slots.end(),
[&](int32_t slot) { return slot == target_slot; })) {
FATAL("Existing move from %s to stack slot %d", RegisterName(reg),
target_slot);
}
}
for (auto& [stack_slot, targets] : moves_from_stack_slot_) {
auto& stack_slots = targets.stack_slots;
if (std::any_of(stack_slots.begin(), stack_slots.end(),
[&](int32_t slot) { return slot == target_slot; })) {
FATAL("Existing move from stack slot %d to stack slot %d", stack_slot,
target_slot);
}
}
for (auto& [stack_slot, node] : materializing_stack_slot_moves_) {
if (stack_slot == target_slot) {
FATAL("Existing materialization of %p to stack slot %d", node,
stack_slot);
}
}
}
#else
void CheckNoExistingMoveToRegister(RegisterT target_reg) {}
void CheckNoExistingMoveToStackSlot(int32_t target_slot) {}
#endif
void RecordMoveToRegister(ValueNode* node,
compiler::InstructionOperand source,
RegisterT target_reg,
bool target_needs_to_be_decompressed) {
CheckNoExistingMoveToRegister(target_reg);
NeedsDecompression needs_decompression = kDoesNotNeedDecompression;
if constexpr (DecompressIfNeeded) {
if (target_needs_to_be_decompressed &&
!node->decompresses_tagged_result()) {
needs_decompression = kNeedsDecompression;
}
} else {
DCHECK_IMPLIES(target_needs_to_be_decompressed,
node->decompresses_tagged_result());
}
GapMoveTargets* targets;
if (source.IsAnyRegister()) {
RegisterT source_reg = ToRegisterT<RegisterT>(source);
if (target_reg == source_reg) {
DCHECK_EQ(needs_decompression, kDoesNotNeedDecompression);
return;
}
targets = &moves_from_register_[source_reg.code()];
} else if (source.IsAnyStackSlot()) {
int32_t source_slot = masm_->GetFramePointerOffsetForStackSlot(
compiler::AllocatedOperand::cast(source));
targets = &moves_from_stack_slot_[source_slot];
} else {
DCHECK(source.IsConstant());
DCHECK(IsConstantNode(node->opcode()));
materializing_register_moves_[target_reg.code()] = node;
return;
}
targets->registers.set(target_reg);
if (needs_decompression == kNeedsDecompression) {
targets->needs_decompression = kNeedsDecompression;
}
}
void RecordMoveToStackSlot(ValueNode* node,
compiler::InstructionOperand source,
int32_t target_slot,
bool target_needs_to_be_decompressed) {
CheckNoExistingMoveToStackSlot(target_slot);
NeedsDecompression needs_decompression = kDoesNotNeedDecompression;
if constexpr (DecompressIfNeeded) {
if (target_needs_to_be_decompressed &&
!node->decompresses_tagged_result()) {
needs_decompression = kNeedsDecompression;
}
} else {
DCHECK_IMPLIES(target_needs_to_be_decompressed,
node->decompresses_tagged_result());
}
GapMoveTargets* targets;
if (source.IsAnyRegister()) {
RegisterT source_reg = ToRegisterT<RegisterT>(source);
targets = &moves_from_register_[source_reg.code()];
} else if (source.IsAnyStackSlot()) {
int32_t source_slot = masm_->GetFramePointerOffsetForStackSlot(
compiler::AllocatedOperand::cast(source));
if (source_slot == target_slot &&
needs_decompression == kDoesNotNeedDecompression) {
return;
}
targets = &moves_from_stack_slot_[source_slot];
} else {
DCHECK(source.IsConstant());
DCHECK(IsConstantNode(node->opcode()));
materializing_stack_slot_moves_.emplace_back(target_slot, node);
return;
}
targets->stack_slots.push_back(target_slot);
if (needs_decompression == kNeedsDecompression) {
targets->needs_decompression = kNeedsDecompression;
}
}
GapMoveTargets PopTargets(RegisterT source_reg) {
return std::exchange(moves_from_register_[source_reg.code()],
GapMoveTargets{});
}
GapMoveTargets PopTargets(int32_t source_slot) {
auto handle = moves_from_stack_slot_.extract(source_slot);
if (handle.empty()) return {};
DCHECK(!handle.mapped().is_empty());
return std::move(handle.mapped());
}
template <typename SourceT>
void StartEmitMoveChain(SourceT source) {
DCHECK(!scratch_has_cycle_start_);
GapMoveTargets targets = PopTargets(source);
if (targets.is_empty()) return;
bool has_cycle = RecursivelyEmitMoveChainTargets(source, targets);
if (has_cycle) {
if (!scratch_has_cycle_start_) {
Pop(scratch_);
scratch_has_cycle_start_ = true;
}
EmitMovesFromSource(scratch_, std::move(targets));
scratch_has_cycle_start_ = false;
__ RecordComment("-- * End of cycle");
} else {
EmitMovesFromSource(source, std::move(targets));
__ RecordComment("-- * Chain emitted with no cycles");
}
}
template <typename ChainStartT, typename SourceT>
bool ContinueEmitMoveChain(ChainStartT chain_start, SourceT source) {
if constexpr (std::is_same_v<ChainStartT, SourceT>) {
if (chain_start == source) {
__ RecordComment("-- * Cycle");
DCHECK(!scratch_has_cycle_start_);
if constexpr (std::is_same_v<ChainStartT, int32_t>) {
__ Move(scratch_, StackSlot{chain_start});
} else {
__ Move(scratch_, chain_start);
}
scratch_has_cycle_start_ = true;
return true;
}
}
GapMoveTargets targets = PopTargets(source);
if (targets.is_empty()) {
__ RecordComment("-- * End of chain");
return false;
}
bool has_cycle = RecursivelyEmitMoveChainTargets(chain_start, targets);
EmitMovesFromSource(source, std::move(targets));
return has_cycle;
}
template <typename ChainStartT>
bool RecursivelyEmitMoveChainTargets(ChainStartT chain_start,
GapMoveTargets& targets) {
bool has_cycle = false;
for (auto target : targets.registers) {
has_cycle |= ContinueEmitMoveChain(chain_start, target);
}
for (int32_t target_slot : targets.stack_slots) {
has_cycle |= ContinueEmitMoveChain(chain_start, target_slot);
}
return has_cycle;
}
void EmitMovesFromSource(RegisterT source_reg, GapMoveTargets&& targets) {
DCHECK(moves_from_register_[source_reg.code()].is_empty());
if constexpr (DecompressIfNeeded) {
static_assert(DecompressIfNeeded && COMPRESS_POINTERS_BOOL);
if (targets.needs_decompression == kNeedsDecompression) {
__ DecompressTagged(source_reg, source_reg);
}
}
for (RegisterT target_reg : targets.registers) {
DCHECK(moves_from_register_[target_reg.code()].is_empty());
__ Move(target_reg, source_reg);
}
for (int32_t target_slot : targets.stack_slots) {
DCHECK_EQ(moves_from_stack_slot_.find(target_slot),
moves_from_stack_slot_.end());
__ Move(StackSlot{target_slot}, source_reg);
}
}
void EmitMovesFromSource(int32_t source_slot, GapMoveTargets&& targets) {
DCHECK_EQ(moves_from_stack_slot_.find(source_slot),
moves_from_stack_slot_.end());
RegisterT register_with_slot_value = RegisterT::no_reg();
if (!targets.registers.is_empty()) {
register_with_slot_value = targets.registers.PopFirst();
} else {
DCHECK(!targets.stack_slots.empty());
if (scratch_has_cycle_start_) {
Push(scratch_);
scratch_has_cycle_start_ = false;
}
register_with_slot_value = scratch_;
}
DCHECK(register_with_slot_value.is_valid());
DCHECK(moves_from_register_[register_with_slot_value.code()].is_empty());
__ Move(register_with_slot_value, StackSlot{source_slot});
if constexpr (DecompressIfNeeded) {
static_assert(DecompressIfNeeded && COMPRESS_POINTERS_BOOL);
if (targets.needs_decompression == kNeedsDecompression) {
__ DecompressTagged(register_with_slot_value, register_with_slot_value);
targets.needs_decompression = kDoesNotNeedDecompression;
}
}
EmitMovesFromSource(register_with_slot_value, std::move(targets));
}
void Push(Register reg) { __ Push(reg); }
void Push(DoubleRegister reg) { __ PushAll({reg}); }
void Pop(Register reg) { __ Pop(reg); }
void Pop(DoubleRegister reg) { __ PopAll({reg}); }
MaglevAssembler* masm() const { return masm_; }
MaglevAssembler* const masm_;
RegisterT scratch_;
std::array<GapMoveTargets, RegisterT::kNumRegisters> moves_from_register_ =
{};
std::unordered_map<int32_t, GapMoveTargets> moves_from_stack_slot_;
std::array<ValueNode*, RegisterT::kNumRegisters>
materializing_register_moves_ = {};
std::vector<std::pair<int32_t, ValueNode*>> materializing_stack_slot_moves_;
bool scratch_has_cycle_start_ = false;
};
class ExceptionHandlerTrampolineBuilder {
public:
static void Build(MaglevAssembler* masm, NodeBase* node) {
ExceptionHandlerTrampolineBuilder builder(masm);
builder.EmitTrampolineFor(node);
}
private:
explicit ExceptionHandlerTrampolineBuilder(MaglevAssembler* masm)
: masm_(masm) {}
struct Move {
explicit Move(const ValueLocation& target, ValueNode* source)
: target(target), source(source) {}
const ValueLocation& target;
ValueNode* const source;
};
using MoveVector = base::SmallVector<Move, 16>;
void EmitTrampolineFor(NodeBase* node) {
DCHECK(node->properties().can_throw());
ExceptionHandlerInfo* const handler_info = node->exception_handler_info();
if (handler_info->ShouldLazyDeopt()) return;
DCHECK(handler_info->HasExceptionHandler());
BasicBlock* const catch_block = handler_info->catch_block();
LazyDeoptInfo* const deopt_info = node->lazy_deopt_info();
const InterpretedDeoptFrame& lazy_frame =
deopt_info->GetFrameForExceptionHandler(handler_info);
ParallelMoveResolver<Register, COMPRESS_POINTERS_BOOL> direct_moves(masm_);
MoveVector materialising_moves;
bool save_accumulator = false;
RecordMoves(lazy_frame.unit(), catch_block, lazy_frame.frame_state(),
&direct_moves, &materialising_moves, &save_accumulator);
__ BindJumpTarget(&handler_info->trampoline_entry());
__ RecordComment("-- Exception handler trampoline START");
EmitMaterialisationsAndPushResults(materialising_moves, save_accumulator);
__ RecordComment("EmitMoves");
MaglevAssembler::TemporaryRegisterScope temps(masm_);
Register scratch = temps.AcquireScratch();
direct_moves.EmitMoves(scratch);
EmitPopMaterialisedResults(materialising_moves, save_accumulator, scratch);
__ Jump(catch_block->label());
__ RecordComment("-- Exception handler trampoline END");
}
MaglevAssembler* masm() const { return masm_; }
void RecordMoves(
const MaglevCompilationUnit& unit, BasicBlock* catch_block,
const CompactInterpreterFrameState* register_frame,
ParallelMoveResolver<Register, COMPRESS_POINTERS_BOOL>* direct_moves,
MoveVector* materialising_moves, bool* save_accumulator) {
if (!catch_block->has_phi()) return;
for (Phi* phi : *catch_block->phis()) {
DCHECK(phi->is_exception_phi());
if (!phi->has_valid_live_range()) continue;
const ValueLocation& target = phi->result();
if (phi->owner() == interpreter::Register::virtual_accumulator()) {
DCHECK_EQ(target.AssignedGeneralRegister(), kReturnRegister0);
*save_accumulator = true;
continue;
}
ValueNode* source = register_frame->GetValueOf(phi->owner(), unit);
DCHECK_NOT_NULL(source);
if (VirtualObject* vobj = source->TryCast<VirtualObject>()) {
DCHECK(vobj->allocation()->HasEscaped());
source = vobj->allocation();
}
DCHECK(!source->regalloc_info()->allocation().IsRegister());
DCHECK(!source->Is<Identity>());
switch (source->properties().value_representation()) {
case ValueRepresentation::kTagged:
direct_moves->RecordMove(
source, source->regalloc_info()->allocation(),
compiler::AllocatedOperand::cast(target.operand()),
phi->decompresses_tagged_result() ? kNeedsDecompression
: kDoesNotNeedDecompression);
break;
case ValueRepresentation::kInt32:
case ValueRepresentation::kUint32:
case ValueRepresentation::kIntPtr:
case ValueRepresentation::kRawPtr:
materialising_moves->emplace_back(target, source);
break;
case ValueRepresentation::kFloat64:
case ValueRepresentation::kHoleyFloat64:
materialising_moves->emplace_back(target, source);
break;
case ValueRepresentation::kShiftedInt53:
case ValueRepresentation::kNone:
UNREACHABLE();
}
}
}
void EmitMaterialisationsAndPushResults(const MoveVector& moves,
bool save_accumulator) const {
if (moves.empty()) return;
__ RecordComment("EmitMaterialisationsAndPushResults");
if (save_accumulator) __ Push(kReturnRegister0);
#ifdef DEBUG
__ set_allow_call(true);
#endif
for (const Move& move : moves) {
if (IsConstantNode(move.source->opcode())) continue;
__ MaterialiseValueNode(kReturnRegister0, move.source);
__ Push(kReturnRegister0);
}
#ifdef DEBUG
__ set_allow_call(false);
#endif
}
void EmitPopMaterialisedResults(const MoveVector& moves,
bool save_accumulator,
Register scratch) const {
if (moves.empty()) return;
__ RecordComment("EmitPopMaterialisedResults");
for (const Move& move : base::Reversed(moves)) {
const ValueLocation& target = move.target;
Register target_reg = target.operand().IsAnyRegister()
? target.AssignedGeneralRegister()
: scratch;
if (IsConstantNode(move.source->opcode())) {
__ MaterialiseValueNode(target_reg, move.source);
} else {
__ Pop(target_reg);
}
if (target_reg == scratch) {
__ Move(masm_->ToMemOperand(target.operand()), scratch);
}
}
if (save_accumulator) __ Pop(kReturnRegister0);
}
MaglevAssembler* const masm_;
};
class MaglevCodeGeneratingNodeProcessor {
public:
MaglevCodeGeneratingNodeProcessor(MaglevAssembler* masm, Zone* zone)
: masm_(masm),
zone_(zone),
collect_source_positions_(masm->code_gen_state()
->compilation_info()
->collect_source_positions()) {
DCHECK_IMPLIES(collect_source_positions_, graph_labeller() != nullptr);
}
void PreProcessGraph(Graph* graph) {
code_gen_state()->set_untagged_slots(graph->untagged_stack_slots());
code_gen_state()->set_tagged_slots(graph->tagged_stack_slots());
code_gen_state()->set_max_deopted_stack_size(
graph->max_deopted_stack_size());
code_gen_state()->set_max_call_stack_args_(graph->max_call_stack_args());
if (v8_flags.maglev_break_on_entry) {
__ DebugBreak();
}
if (graph->is_osr()) {
__ OSRPrologue(graph);
} else {
__ Prologue(graph);
}
__ set_has_frame(true);
int deferred_count = ComputeDeferred(graph);
if (graph->blocks()[0]->is_deferred()) {
graph->blocks()[0]->set_deferred(false);
--deferred_count;
}
int non_deferred_count = graph->num_blocks() - deferred_count;
ZoneVector<BasicBlock*> new_blocks(graph->num_blocks(), zone_);
size_t ix_non_deferred = 0;
size_t ix_deferred = non_deferred_count;
for (auto block_it = graph->begin(); block_it != graph->end(); ++block_it) {
BasicBlock* block = *block_it;
DCHECK(!block->is_dead());
if (block->is_deferred()) {
new_blocks[ix_deferred++] = block;
} else {
new_blocks[ix_non_deferred++] = block;
}
}
CHECK_EQ(ix_deferred, graph->num_blocks());
CHECK_EQ(ix_non_deferred, non_deferred_count);
graph->set_blocks(new_blocks);
ZoneVector<BasicBlock*>& blocks = graph->blocks();
size_t current_ix = 0;
for (size_t i = 0; i < blocks.size(); ++i) {
BasicBlock* block = blocks[i];
if (code_gen_state()->RealJumpTarget(block) == block) {
blocks[current_ix++] = block;
}
}
blocks.resize(current_ix);
}
void PostProcessGraph(Graph* graph) {}
void PostProcessBasicBlock(BasicBlock* block) {}
void PostPhiProcessing() {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
if (block->is_loop()) {
__ LoopHeaderAlign();
}
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- Block b" << block->id();
__ RecordComment(ss.str());
}
__ BindBlock(block);
return BlockProcessResult::kContinue;
}
template <typename NodeT>
ProcessResult Process(NodeT* node, const ProcessingState& state) {
#ifdef DEBUG
if constexpr (std::is_base_of_v<ValueNode, NodeT>) {
DCHECK(!node->regalloc_info()->has_register());
}
#endif
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- " << graph_labeller()->NodeId(node) << ": "
<< PrintNode(node);
__ RecordComment(ss.str());
}
if (collect_source_positions_) {
const auto& provenance = graph_labeller()->GetNodeProvenance(node);
if (provenance.position.IsKnown()) {
code_gen_state()->source_position_table_builder()->AddPosition(
masm_->pc_offset(), provenance.position, false);
}
}
if (v8_flags.maglev_assert_stack_size) {
__ AssertStackSizeCorrect();
}
PatchJumps(node);
if (std::is_base_of_v<UnconditionalControlNode, NodeT>) {
EmitBlockEndGapMoves(node->template Cast<UnconditionalControlNode>(),
state);
}
if (v8_flags.slow_debug_code && !std::is_same_v<NodeT, Phi>) {
for (Input input : node->inputs()) {
ValueRepresentation rep =
input.node()->properties().value_representation();
if (IsZeroExtendedRepresentation(rep)) {
if (input.location()->IsGeneralRegister()) {
__ AssertZeroExtended(ToRegister(input));
}
}
}
}
MaglevAssembler::TemporaryRegisterScope scratch_scope(masm());
scratch_scope.Include(node->regalloc_info()->general_temporaries());
scratch_scope.IncludeDouble(node->regalloc_info()->double_temporaries());
#ifdef DEBUG
masm()->set_allow_allocate(node->properties().can_allocate());
masm()->set_allow_call(node->properties().is_call());
masm()->set_allow_deferred_call(node->properties().is_deferred_call());
#endif
node->GenerateCode(masm(), state);
#ifdef DEBUG
masm()->set_allow_allocate(false);
masm()->set_allow_call(false);
masm()->set_allow_deferred_call(false);
#endif
if (std::is_base_of_v<ValueNode, NodeT>) {
ValueNode* value_node = node->template Cast<ValueNode>();
RegallocValueNodeInfo* node_info = value_node->regalloc_info();
if (node_info->has_valid_live_range() && node_info->is_spilled()) {
compiler::AllocatedOperand source =
compiler::AllocatedOperand::cast(node_info->result().operand());
if (!source.IsAnyStackSlot()) {
if (v8_flags.code_comments) __ RecordComment("-- Spill:");
if (source.IsRegister()) {
__ Move(masm()->GetStackSlot(node_info->spill_slot()),
ToRegister(source));
} else {
__ StoreFloat64(masm()->GetStackSlot(node_info->spill_slot()),
ToDoubleRegister(source));
}
} else {
DCHECK_EQ(source.index(), node_info->spill_slot().index());
}
}
}
return ProcessResult::kContinue;
}
void EmitBlockEndGapMoves(UnconditionalControlNode* node,
const ProcessingState& state) {
BasicBlock* target = node->target();
if (!target->has_state()) {
__ RecordComment("-- Target has no state, must be a fallthrough");
return;
}
int predecessor_id = state.block()->predecessor_id();
MaglevAssembler::TemporaryRegisterScope temps(masm_);
Register scratch = temps.AcquireScratch();
DoubleRegister double_scratch = temps.AcquireScratchDouble();
ParallelMoveResolver<Register, false> register_moves(masm_);
ParallelMoveResolver<DoubleRegister, false> double_register_moves(masm_);
RegList registers_set_by_phis;
DoubleRegList double_registers_set_by_phis;
__ RecordComment("-- Gap moves:");
if (target->has_phi()) {
Phi::List* phis = target->phis();
for (Phi* phi : *phis) {
if (!phi->has_valid_live_range()) {
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- * "
<< phi->input(state.block()->predecessor_id()).operand() << " → "
<< target << " (n" << graph_labeller()->NodeId(phi)
<< ") [DEAD]";
__ RecordComment(ss.str());
}
continue;
}
Input input = phi->input(state.block()->predecessor_id());
ValueNode* input_node = input.node();
compiler::InstructionOperand source = input.operand();
compiler::AllocatedOperand target_operand =
compiler::AllocatedOperand::cast(phi->result().operand());
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- * " << source << " → " << target << " (n"
<< graph_labeller()->NodeId(phi) << ")";
__ RecordComment(ss.str());
}
if (phi->use_double_register()) {
DCHECK(!phi->decompresses_tagged_result());
double_register_moves.RecordMove(input_node, source, target_operand,
false);
} else {
register_moves.RecordMove(input_node, source, target_operand,
kDoesNotNeedDecompression);
}
if (target_operand.IsAnyRegister()) {
if (phi->use_double_register()) {
double_registers_set_by_phis.set(
target_operand.GetDoubleRegister());
} else {
registers_set_by_phis.set(target_operand.GetRegister());
}
}
}
}
target->state()->register_state().ForEachGeneralRegister(
[&](Register reg, RegisterState& state) {
if (registers_set_by_phis.has(reg)) return;
ValueNode* node;
RegisterMerge* merge;
if (LoadMergeState(state, &node, &merge)) {
compiler::InstructionOperand source =
merge->operand(predecessor_id);
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- * " << source << " → " << reg;
__ RecordComment(ss.str());
}
register_moves.RecordMove(node, source, reg,
kDoesNotNeedDecompression);
}
});
register_moves.EmitMoves(scratch);
__ RecordComment("-- Double gap moves:");
target->state()->register_state().ForEachDoubleRegister(
[&](DoubleRegister reg, RegisterState& state) {
if (double_registers_set_by_phis.has(reg)) return;
ValueNode* node;
RegisterMerge* merge;
if (LoadMergeState(state, &node, &merge)) {
compiler::InstructionOperand source =
merge->operand(predecessor_id);
if (v8_flags.code_comments) {
std::stringstream ss;
ss << "-- * " << source << " → " << reg;
__ RecordComment(ss.str());
}
double_register_moves.RecordMove(node, source, reg,
kDoesNotNeedDecompression);
}
});
double_register_moves.EmitMoves(double_scratch);
}
Isolate* isolate() const { return masm_->isolate(); }
MaglevAssembler* masm() const { return masm_; }
MaglevCodeGenState* code_gen_state() const {
return masm()->code_gen_state();
}
MaglevGraphLabeller* graph_labeller() const {
return code_gen_state()->graph_labeller();
}
private:
template <typename NodeT>
void PatchJumps(NodeT* node) {
if constexpr (IsUnconditionalControlNode(Node::opcode_of<NodeT>)) {
UnconditionalControlNode* control_node =
node->template Cast<UnconditionalControlNode>();
control_node->set_target(
code_gen_state()->RealJumpTarget(control_node->target()));
} else if constexpr (IsBranchControlNode(Node::opcode_of<NodeT>)) {
BranchControlNode* control_node =
node->template Cast<BranchControlNode>();
control_node->set_if_true(
code_gen_state()->RealJumpTarget(control_node->if_true()));
control_node->set_if_false(
code_gen_state()->RealJumpTarget(control_node->if_false()));
} else if constexpr (Node::opcode_of<NodeT> == Opcode::kSwitch) {
Switch* switch_node = node->template Cast<Switch>();
BasicBlockRef* targets = switch_node->targets();
for (int i = 0; i < switch_node->size(); ++i) {
targets[i].set_block_ptr(
code_gen_state()->RealJumpTarget(targets[i].block_ptr()));
}
if (switch_node->has_fallthrough()) {
switch_node->set_fallthrough(
code_gen_state()->RealJumpTarget(switch_node->fallthrough()));
}
}
}
int ComputeDeferred(Graph* graph) {
int deferred_count = 0;
SmallZoneVector<BasicBlock*, 32> work_queue(zone_);
for (auto block_it = graph->begin(); block_it != graph->end(); ++block_it) {
BasicBlock* block = *block_it;
if (block->is_deferred()) {
++deferred_count;
work_queue.emplace_back(block);
}
}
while (!work_queue.empty()) {
BasicBlock* block = work_queue.back();
work_queue.pop_back();
DCHECK(block->is_deferred());
block->ForEachSuccessor([&work_queue,
&deferred_count](BasicBlock* successor) {
if (successor->is_deferred()) {
return;
}
bool should_defer = true;
successor->ForEachPredecessor([&should_defer](BasicBlock* predecessor) {
if (!predecessor->is_deferred()) {
should_defer = false;
}
});
if (should_defer) {
++deferred_count;
work_queue.emplace_back(successor);
successor->set_deferred(true);
}
});
block->ForEachPredecessor([&work_queue,
&deferred_count](BasicBlock* predecessor) {
if (predecessor->is_deferred()) {
return;
}
bool should_defer = true;
predecessor->ForEachSuccessor([&should_defer](BasicBlock* successor) {
if (!successor->is_deferred()) {
should_defer = false;
}
});
if (should_defer) {
++deferred_count;
work_queue.emplace_back(predecessor);
predecessor->set_deferred(true);
}
});
}
return deferred_count;
}
MaglevAssembler* const masm_;
Zone* zone_;
bool collect_source_positions_;
};
class SafepointingNodeProcessor {
public:
explicit SafepointingNodeProcessor(LocalIsolate* local_isolate)
: local_isolate_(local_isolate) {}
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) {}
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
ProcessResult Process(NodeBase* node, const ProcessingState& state) {
local_isolate_->heap()->Safepoint();
return ProcessResult::kContinue;
}
private:
LocalIsolate* local_isolate_;
};
namespace {
DeoptimizationFrameTranslation::FrameCount GetFrameCount(
const DeoptFrame* deopt_frame) {
int total = 0;
int js_frame = 0;
do {
if (deopt_frame->IsJsFrame()) {
js_frame++;
}
total++;
deopt_frame = deopt_frame->parent();
} while (deopt_frame);
return {total, js_frame};
}
}
class MaglevFrameTranslationBuilder {
public:
MaglevFrameTranslationBuilder(
LocalIsolate* local_isolate, MaglevAssembler* masm,
FrameTranslationBuilder* translation_array_builder,
IdentityMap<int, base::DefaultAllocationPolicy>* protected_deopt_literals,
IdentityMap<int, base::DefaultAllocationPolicy>* deopt_literals,
ZoneVector<IndirectHandle<TrustedObject>>*
protected_deopt_literals_vector,
ZoneVector<IndirectHandle<Object>>* deopt_literals_vector)
: local_isolate_(local_isolate),
masm_(masm),
translation_array_builder_(translation_array_builder),
protected_deopt_literals_(protected_deopt_literals),
deopt_literals_(deopt_literals),
protected_deopt_literals_vector_(protected_deopt_literals_vector),
deopt_literals_vector_(deopt_literals_vector),
object_ids_(10) {}
void BuildEagerDeopt(EagerDeoptInfo* deopt_info) {
BuildBeginDeopt(deopt_info);
const InputLocation* current_input_location = deopt_info->input_locations();
const VirtualObjectList& virtual_objects =
deopt_info->top_frame().GetVirtualObjects();
RecursiveBuildDeoptFrame(deopt_info->top_frame(), current_input_location,
virtual_objects);
CHECK_EQ(current_input_location, deopt_info->input_locations_end());
}
void BuildLazyDeopt(LazyDeoptInfo* deopt_info) {
BuildBeginDeopt(deopt_info);
const InputLocation* current_input_location = deopt_info->input_locations();
const VirtualObjectList& virtual_objects =
deopt_info->top_frame().GetVirtualObjects();
if (deopt_info->top_frame().parent()) {
RecursiveBuildDeoptFrame(*deopt_info->top_frame().parent(),
current_input_location, virtual_objects);
}
const DeoptFrame& top_frame = deopt_info->top_frame();
switch (top_frame.type()) {
case DeoptFrame::FrameType::kInterpretedFrame:
return BuildSingleDeoptFrame(
top_frame.as_interpreted(), current_input_location, virtual_objects,
deopt_info->result_location(), deopt_info->result_size());
case DeoptFrame::FrameType::kInlinedArgumentsFrame:
UNREACHABLE();
case DeoptFrame::FrameType::kConstructInvokeStubFrame:
return BuildSingleDeoptFrame(top_frame.as_construct_stub(),
current_input_location, virtual_objects);
case DeoptFrame::FrameType::kBuiltinContinuationFrame:
return BuildSingleDeoptFrame(top_frame.as_builtin_continuation(),
current_input_location, virtual_objects);
}
CHECK_EQ(current_input_location, deopt_info->input_locations_end());
}
private:
constexpr int DeoptStackSlotIndexFromFPOffset(int offset) {
return 1 - offset / kSystemPointerSize;
}
int DeoptStackSlotFromStackSlot(const compiler::AllocatedOperand& operand) {
return DeoptStackSlotIndexFromFPOffset(
masm_->GetFramePointerOffsetForStackSlot(operand));
}
void BuildBeginDeopt(DeoptInfo* deopt_info) {
object_ids_.clear();
auto [frame_count, jsframe_count] = GetFrameCount(&deopt_info->top_frame());
deopt_info->set_translation_index(
translation_array_builder_->BeginTranslation(
frame_count, jsframe_count,
deopt_info->feedback_to_update().IsValid()));
if (deopt_info->feedback_to_update().IsValid()) {
translation_array_builder_->AddUpdateFeedback(
GetDeoptLiteral(*deopt_info->feedback_to_update().vector),
deopt_info->feedback_to_update().index());
}
}
void RecursiveBuildDeoptFrame(const DeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects) {
if (frame.parent()) {
RecursiveBuildDeoptFrame(*frame.parent(), current_input_location,
virtual_objects);
}
switch (frame.type()) {
case DeoptFrame::FrameType::kInterpretedFrame:
return BuildSingleDeoptFrame(frame.as_interpreted(),
current_input_location, virtual_objects);
case DeoptFrame::FrameType::kInlinedArgumentsFrame:
return BuildSingleDeoptFrame(frame.as_inlined_arguments(),
current_input_location, virtual_objects);
case DeoptFrame::FrameType::kConstructInvokeStubFrame:
return BuildSingleDeoptFrame(frame.as_construct_stub(),
current_input_location, virtual_objects);
case DeoptFrame::FrameType::kBuiltinContinuationFrame:
return BuildSingleDeoptFrame(frame.as_builtin_continuation(),
current_input_location, virtual_objects);
}
}
void BuildSingleDeoptFrame(const InterpretedDeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects,
interpreter::Register result_location,
int result_size) {
int return_offset = frame.ComputeReturnOffset(result_location, result_size);
translation_array_builder_->BeginInterpretedFrame(
frame.bytecode_position(),
GetDeoptLiteral(frame.GetSharedFunctionInfo()),
GetProtectedDeoptLiteral(*frame.GetBytecodeArray().object()),
frame.unit().register_count(), return_offset, result_size);
BuildDeoptFrameValues(frame.unit(), frame.frame_state(), frame.closure(),
current_input_location, virtual_objects);
}
void BuildSingleDeoptFrame(const InterpretedDeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects) {
const int return_offset = 0;
const int return_count = 0;
translation_array_builder_->BeginInterpretedFrame(
frame.bytecode_position(),
GetDeoptLiteral(frame.GetSharedFunctionInfo()),
GetProtectedDeoptLiteral(*frame.GetBytecodeArray().object()),
frame.unit().register_count(), return_offset, return_count);
BuildDeoptFrameValues(frame.unit(), frame.frame_state(), frame.closure(),
current_input_location, virtual_objects);
}
void BuildSingleDeoptFrame(const InlinedArgumentsDeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects) {
translation_array_builder_->BeginInlinedExtraArguments(
GetDeoptLiteral(frame.GetSharedFunctionInfo()),
static_cast<uint32_t>(frame.arguments().size()),
frame.GetBytecodeArray().parameter_count());
BuildDeoptFrameSingleValue(frame.closure(), current_input_location,
virtual_objects);
for (ValueNode* value : frame.arguments()) {
BuildDeoptFrameSingleValue(value, current_input_location,
virtual_objects);
}
}
void BuildSingleDeoptFrame(const ConstructInvokeStubDeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects) {
translation_array_builder_->BeginConstructInvokeStubFrame(
GetDeoptLiteral(frame.GetSharedFunctionInfo()));
BuildDeoptFrameSingleValue(frame.receiver(), current_input_location,
virtual_objects);
BuildDeoptFrameSingleValue(frame.context(), current_input_location,
virtual_objects);
}
void BuildSingleDeoptFrame(const BuiltinContinuationDeoptFrame& frame,
const InputLocation*& current_input_location,
const VirtualObjectList& virtual_objects) {
BytecodeOffset bailout_id =
Builtins::GetContinuationBytecodeOffset(frame.builtin_id());
int literal_id = GetDeoptLiteral(frame.GetSharedFunctionInfo());
constexpr int kFixedJSFrameRegisterParameters =
JSTrampolineDescriptor::GetRegisterParameterCount();
if (frame.is_javascript()) {
translation_array_builder_->BeginJavaScriptBuiltinContinuationFrame(
bailout_id, literal_id,
frame.parameters().length() + kFixedJSFrameRegisterParameters);
} else {
translation_array_builder_->BeginBuiltinContinuationFrame(
bailout_id, literal_id, frame.parameters().length());
}
if (frame.is_javascript()) {
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(frame.javascript_target()));
} else {
translation_array_builder_->StoreOptimizedOut();
}
static_assert(TranslatedFrame::kReceiverIsFirstParameterInJSFrames);
for (ValueNode* value : frame.parameters()) {
BuildDeoptFrameSingleValue(value, current_input_location,
virtual_objects);
}
if (frame.is_javascript()) {
DCHECK_EQ(Builtins::CallInterfaceDescriptorFor(frame.builtin_id())
.GetRegisterParameterCount(),
kFixedJSFrameRegisterParameters);
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(frame.javascript_target()));
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(ReadOnlyRoots(local_isolate_).undefined_value()));
translation_array_builder_->StoreLiteral(GetDeoptLiteral(
Smi::FromInt(Builtins::GetStackParameterCount(frame.builtin_id()))));
#ifdef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(Smi::FromInt(kInvalidDispatchHandle.value())));
static_assert(kFixedJSFrameRegisterParameters == 4);
#else
static_assert(kFixedJSFrameRegisterParameters == 3);
#endif
}
ValueNode* value = frame.context();
BuildDeoptFrameSingleValue(value, current_input_location, virtual_objects);
}
void BuildDeoptStoreRegister(const compiler::AllocatedOperand& operand,
ValueRepresentation repr) {
switch (repr) {
case ValueRepresentation::kIntPtr:
translation_array_builder_->StoreIntPtrRegister(operand.GetRegister());
break;
case ValueRepresentation::kTagged:
translation_array_builder_->StoreRegister(operand.GetRegister());
break;
case ValueRepresentation::kInt32:
translation_array_builder_->StoreInt32Register(operand.GetRegister());
break;
case ValueRepresentation::kUint32:
translation_array_builder_->StoreUint32Register(operand.GetRegister());
break;
case ValueRepresentation::kFloat64:
translation_array_builder_->StoreDoubleRegister(
operand.GetDoubleRegister());
break;
case ValueRepresentation::kHoleyFloat64:
translation_array_builder_->StoreHoleyDoubleRegister(
operand.GetDoubleRegister());
break;
case ValueRepresentation::kShiftedInt53:
case ValueRepresentation::kRawPtr:
case ValueRepresentation::kNone:
UNREACHABLE();
}
}
void BuildDeoptStoreStackSlot(const compiler::AllocatedOperand& operand,
ValueRepresentation repr) {
int stack_slot = DeoptStackSlotFromStackSlot(operand);
switch (repr) {
case ValueRepresentation::kIntPtr:
translation_array_builder_->StoreIntPtrStackSlot(stack_slot);
break;
case ValueRepresentation::kTagged:
translation_array_builder_->StoreStackSlot(stack_slot);
break;
case ValueRepresentation::kInt32:
translation_array_builder_->StoreInt32StackSlot(stack_slot);
break;
case ValueRepresentation::kUint32:
translation_array_builder_->StoreUint32StackSlot(stack_slot);
break;
case ValueRepresentation::kFloat64:
translation_array_builder_->StoreDoubleStackSlot(stack_slot);
break;
case ValueRepresentation::kHoleyFloat64:
translation_array_builder_->StoreHoleyDoubleStackSlot(stack_slot);
break;
case ValueRepresentation::kShiftedInt53:
case ValueRepresentation::kRawPtr:
case ValueRepresentation::kNone:
UNREACHABLE();
}
}
int GetDuplicatedId(intptr_t id) {
for (int idx = 0; idx < static_cast<int>(object_ids_.size()); idx++) {
if (object_ids_[idx] == id) {
object_ids_.push_back(id);
return idx;
}
}
object_ids_.push_back(id);
return kNotDuplicated;
}
void BuildHeapNumber(const VirtualObject* vobject) {
DCHECK_EQ(vobject->object_type(), vobj::ObjectType::kHeapNumber);
ValueNode* value_node = vobject->get(HeapNumber::kValueOffset);
return BuildHeapNumber(value_node->Cast<Float64Constant>()->value());
}
void BuildHeapNumber(Float64 number) {
DirectHandle<Object> value =
local_isolate_->factory()->NewHeapNumberFromBits<AllocationType::kOld>(
number.get_bits());
translation_array_builder_->StoreLiteral(GetDeoptLiteral(*value));
}
void BuildNestedValue(const ValueNode* value,
const InputLocation*& input_location,
const VirtualObjectList& virtual_objects) {
const Opcode opcode = value->opcode();
DCHECK_NE(opcode, Opcode::kIdentity);
if (IsConstantNode(opcode)) {
if (opcode == Opcode::kFloat64Constant) {
Float64 value_as_float = value->Cast<Float64Constant>()->value();
if (value_as_float.is_hole_nan()) {
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(ReadOnlyRoots{local_isolate_}.the_hole_value()));
return;
}
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
DCHECK(!value_as_float.is_undefined_nan());
#endif
}
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(*value->Reify(local_isolate_)));
return;
}
switch (opcode) {
case Opcode::kArgumentsElements:
translation_array_builder_->ArgumentsElements(
value->Cast<ArgumentsElements>()->create_arguments_type());
object_ids_.push_back(-1);
break;
case Opcode::kArgumentsLength:
translation_array_builder_->ArgumentsLength();
break;
case Opcode::kRestLength:
translation_array_builder_->RestLength();
break;
case Opcode::kVirtualObject:
UNREACHABLE();
default:
BuildDeoptFrameSingleValue(value, input_location, virtual_objects);
break;
}
}
void BuildVirtualObject(const VirtualObject* object,
const InputLocation*& input_location,
const VirtualObjectList& virtual_objects) {
vobj::ObjectType object_type = object->object_type();
if (object_type == vobj::ObjectType::kHeapNumber) {
return BuildHeapNumber(object);
}
int dup_id =
GetDuplicatedId(reinterpret_cast<intptr_t>(object->allocation()));
if (dup_id != kNotDuplicated) {
translation_array_builder_->DuplicateObject(dup_id);
object->ForEachNestedRuntimeInput(
virtual_objects, [&](ValueNode*) { input_location++; },
VirtualObject::ForEachSlotIterationMode::kForDeopt);
return;
}
if (object_type == vobj::ObjectType::kConsString) {
translation_array_builder_->StringConcat();
} else {
translation_array_builder_->BeginCapturedObject(object->slot_count());
}
auto callback = [&](ValueNode* node, const vobj::Field& desc) -> bool {
BuildNestedValue(node, input_location, virtual_objects);
return true;
};
object->ForEachSlot(callback,
VirtualObject::ForEachSlotIterationMode::kForDeopt);
}
void BuildDeoptFrameSingleValue(const ValueNode* value,
const InputLocation*& input_location,
const VirtualObjectList& virtual_objects) {
value = value->UnwrapIdentities();
DCHECK(!value->Is<VirtualObject>());
if (const InlinedAllocation* alloc = value->TryCast<InlinedAllocation>()) {
VirtualObject* vobject = virtual_objects.FindAllocatedWith(alloc);
if (vobject && alloc->HasBeenElided()) {
DCHECK(alloc->HasBeenAnalysed());
BuildVirtualObject(vobject, input_location, virtual_objects);
return;
}
}
if (input_location->operand().IsConstant()) {
translation_array_builder_->StoreLiteral(
GetDeoptLiteral(*value->Reify(local_isolate_)));
} else {
const compiler::AllocatedOperand& operand =
compiler::AllocatedOperand::cast(input_location->operand());
ValueRepresentation repr = value->properties().value_representation();
if (operand.IsAnyRegister()) {
BuildDeoptStoreRegister(operand, repr);
} else {
BuildDeoptStoreStackSlot(operand, repr);
}
}
input_location++;
}
void BuildDeoptFrameValues(
const MaglevCompilationUnit& compilation_unit,
const CompactInterpreterFrameState* checkpoint_state,
const ValueNode* closure, const InputLocation*& input_location,
const VirtualObjectList& virtual_objects) {
BuildDeoptFrameSingleValue(closure, input_location, virtual_objects);
{
int i = 0;
checkpoint_state->ForEachParameter(
compilation_unit, [&](ValueNode* value, interpreter::Register reg) {
DCHECK_EQ(reg.ToParameterIndex(), i);
BuildDeoptFrameSingleValue(value, input_location, virtual_objects);
i++;
});
}
ValueNode* context_value = checkpoint_state->context(compilation_unit);
BuildDeoptFrameSingleValue(context_value, input_location, virtual_objects);
{
int i = 0;
checkpoint_state->ForEachLocal(
compilation_unit, [&](ValueNode* value, interpreter::Register reg) {
DCHECK_LE(i, reg.index());
while (i < reg.index()) {
translation_array_builder_->StoreOptimizedOut();
i++;
}
DCHECK_EQ(i, reg.index());
BuildDeoptFrameSingleValue(value, input_location, virtual_objects);
i++;
});
while (i < compilation_unit.register_count()) {
translation_array_builder_->StoreOptimizedOut();
i++;
}
}
{
if (checkpoint_state->liveness()->AccumulatorIsLive()) {
ValueNode* value = checkpoint_state->accumulator(compilation_unit);
BuildDeoptFrameSingleValue(value, input_location, virtual_objects);
} else {
translation_array_builder_->StoreOptimizedOut();
}
}
}
int GetProtectedDeoptLiteral(Tagged<TrustedObject> obj) {
IdentityMapFindResult<int> res =
protected_deopt_literals_->FindOrInsert(obj);
if (!res.already_exists) {
DCHECK_EQ(0, *res.entry);
IndirectHandle<TrustedObject> canonical =
masm_->code_gen_state()
->compilation_info()
->broker()
->CanonicalPersistentHandle(obj);
protected_deopt_literals_vector_->push_back(canonical);
*res.entry =
static_cast<int>(protected_deopt_literals_vector_->size() - 1);
}
return *res.entry;
}
int GetDeoptLiteral(Tagged<Object> obj) {
IdentityMapFindResult<int> res = deopt_literals_->FindOrInsert(obj);
if (!res.already_exists) {
DCHECK_EQ(0, *res.entry);
IndirectHandle<Object> canonical = masm_->code_gen_state()
->compilation_info()
->broker()
->CanonicalPersistentHandle(obj);
deopt_literals_vector_->push_back(canonical);
*res.entry = static_cast<int>(deopt_literals_vector_->size() - 1);
}
return *res.entry;
}
int GetDeoptLiteral(compiler::HeapObjectRef ref) {
IdentityMapFindResult<int> res =
deopt_literals_->FindOrInsert(*ref.object());
if (!res.already_exists) {
DCHECK_EQ(0, *res.entry);
deopt_literals_vector_->push_back(ref.object());
*res.entry = static_cast<int>(deopt_literals_vector_->size() - 1);
}
return *res.entry;
}
LocalIsolate* local_isolate_;
MaglevAssembler* masm_;
FrameTranslationBuilder* translation_array_builder_;
IdentityMap<int, base::DefaultAllocationPolicy>* protected_deopt_literals_;
IdentityMap<int, base::DefaultAllocationPolicy>* deopt_literals_;
ZoneVector<IndirectHandle<TrustedObject>>* protected_deopt_literals_vector_;
ZoneVector<IndirectHandle<Object>>* deopt_literals_vector_;
static const int kNotDuplicated = -1;
std::vector<intptr_t> object_ids_;
};
}
MaglevCodeGenerator::MaglevCodeGenerator(
LocalIsolate* isolate, MaglevCompilationInfo* compilation_info,
Graph* graph)
: local_isolate_(isolate),
safepoint_table_builder_(compilation_info->zone(),
graph->tagged_stack_slots()),
frame_translation_builder_(compilation_info->zone()),
source_position_table_builder_(compilation_info->zone()),
code_gen_state_(compilation_info, &safepoint_table_builder_,
&source_position_table_builder_, graph->max_block_id()),
masm_(isolate->GetMainThreadIsolateUnsafe(), compilation_info->zone(),
&code_gen_state_),
graph_(graph),
protected_deopt_literals_(isolate->heap()->heap()),
deopt_literals_(isolate->heap()->heap()),
protected_deopt_literals_vector_(compilation_info->zone()),
deopt_literals_vector_(compilation_info->zone()),
retained_maps_(isolate->heap()),
is_context_specialized_(
compilation_info->specialize_to_function_context()),
zone_(compilation_info->zone()) {
DCHECK(maglev::IsMaglevEnabled());
DCHECK_IMPLIES(compilation_info->toplevel_is_osr(),
maglev::IsMaglevOsrEnabled());
}
bool MaglevCodeGenerator::Assemble() {
if (!EmitCode()) {
__ ClearInternalState();
return false;
}
EmitMetadata();
if (v8_flags.maglev_build_code_on_background) {
code_ = local_isolate_->heap()->NewPersistentMaybeHandle(
BuildCodeObject(local_isolate_));
Handle<Code> code;
if (code_.ToHandle(&code)) {
retained_maps_ = CollectRetainedMaps(code);
}
} else if (v8_flags.maglev_deopt_data_on_background) {
deopt_data_ = local_isolate_->heap()->NewPersistentHandle(
GenerateDeoptimizationData(local_isolate_));
}
return true;
}
MaybeHandle<Code> MaglevCodeGenerator::Generate(Isolate* isolate) {
if (v8_flags.maglev_build_code_on_background) {
Handle<Code> code;
if (code_.ToHandle(&code)) {
return handle(*code, isolate);
}
return kNullMaybeHandle;
}
return BuildCodeObject(isolate->main_thread_local_isolate());
}
GlobalHandleVector<Map> MaglevCodeGenerator::RetainedMaps(Isolate* isolate) {
DisallowGarbageCollection no_gc;
GlobalHandleVector<Map> maps(isolate->heap());
maps.Reserve(retained_maps_.size());
for (DirectHandle<Map> map : retained_maps_) maps.Push(*map);
return maps;
}
bool MaglevCodeGenerator::EmitCode() {
GraphProcessor<NodeMultiProcessor<SafepointingNodeProcessor,
MaglevCodeGeneratingNodeProcessor>>
processor(SafepointingNodeProcessor{local_isolate_},
MaglevCodeGeneratingNodeProcessor{masm(), zone_});
RecordInlinedFunctions();
if (graph_->is_osr()) {
masm_.Abort(AbortReason::kShouldNotDirectlyEnterOsrFunction);
masm_.RecordComment("-- OSR entrypoint --");
masm_.BindJumpTarget(code_gen_state_.osr_entry());
}
processor.ProcessGraph(graph_);
EmitDeferredCode();
if (!EmitDeopts()) return false;
EmitExceptionHandlerTrampolines();
__ FinishCode();
code_gen_succeeded_ = true;
return true;
}
void MaglevCodeGenerator::RecordInlinedFunctions() {
DCHECK_EQ(0u, deopt_literals_.size());
DCHECK_EQ(0u, deopt_literals_vector_.size());
for (OptimizedCompilationInfo::InlinedFunctionHolder& inlined :
graph_->inlined_functions()) {
IdentityMapFindResult<int> res =
deopt_literals_.FindOrInsert(inlined.shared_info);
if (!res.already_exists) {
DCHECK_EQ(0, *res.entry);
deopt_literals_vector_.push_back(inlined.shared_info);
*res.entry = static_cast<int>(deopt_literals_vector_.size() - 1);
}
inlined.RegisterInlinedFunctionId(*res.entry);
}
inlined_function_count_ = static_cast<int>(deopt_literals_vector_.size());
}
void MaglevCodeGenerator::EmitDeferredCode() {
while (!code_gen_state_.deferred_code().empty()) {
for (DeferredCodeInfo* deferred_code : code_gen_state_.TakeDeferredCode()) {
__ RecordComment("-- Deferred block");
__ bind(&deferred_code->deferred_code_label);
deferred_code->Generate(masm());
__ Trap();
}
}
}
bool MaglevCodeGenerator::EmitDeopts() {
const size_t num_deopts = code_gen_state_.eager_deopts().size() +
code_gen_state_.lazy_deopts().size();
if (num_deopts > Deoptimizer::kMaxNumberOfEntries) {
return false;
}
MaglevFrameTranslationBuilder translation_builder(
local_isolate_, &masm_, &frame_translation_builder_,
&protected_deopt_literals_, &deopt_literals_,
&protected_deopt_literals_vector_, &deopt_literals_vector_);
Label eager_deopt_entry;
Label lazy_deopt_entry;
__ MaybeEmitDeoptBuiltinsCall(
code_gen_state_.eager_deopts().size(), &eager_deopt_entry,
code_gen_state_.lazy_deopts().size(), &lazy_deopt_entry);
deopt_exit_start_offset_ = __ pc_offset();
int deopt_index = 0;
#ifdef V8_TARGET_ARCH_PPC64
Assembler::BlockTrampolinePoolScope block_trampoline_pool(masm());
#endif
__ RecordComment("-- Non-lazy deopts");
for (EagerDeoptInfo* deopt_info : code_gen_state_.eager_deopts()) {
local_isolate_->heap()->Safepoint();
translation_builder.BuildEagerDeopt(deopt_info);
__ bind(deopt_info->deopt_entry_label());
__ CallForDeoptimization(Builtin::kDeoptimizationEntry_Eager, deopt_index,
deopt_info->deopt_entry_label(),
DeoptimizeKind::kEager, nullptr,
&eager_deopt_entry);
if (masm_.compilation_info()->collect_source_positions() ||
AlwaysPreserveDeoptReason(deopt_info->reason())) {
__ RecordDeoptReason(deopt_info->reason(), 0,
masm_.compilation_info()->collect_source_positions()
? deopt_info->top_frame().GetSourcePosition()
: SourcePosition::Unknown(),
deopt_index);
}
deopt_index++;
}
__ RecordComment("-- Lazy deopts");
int last_updated_safepoint = 0;
for (LazyDeoptInfo* deopt_info : code_gen_state_.lazy_deopts()) {
local_isolate_->heap()->Safepoint();
translation_builder.BuildLazyDeopt(deopt_info);
__ BindExceptionHandler(deopt_info->deopt_entry_label());
__ CallForDeoptimization(Builtin::kDeoptimizationEntry_Lazy, deopt_index,
deopt_info->deopt_entry_label(),
DeoptimizeKind::kLazy, nullptr, &lazy_deopt_entry);
if (masm_.compilation_info()->collect_source_positions()) {
__ RecordDeoptReason(DeoptimizeReason::kUnknown, 0,
deopt_info->top_frame().GetSourcePosition(),
deopt_index);
}
last_updated_safepoint = safepoint_table_builder_.UpdateDeoptimizationInfo(
deopt_info->deopting_call_return_pc(),
deopt_info->deopt_entry_label()->pos(), last_updated_safepoint,
deopt_index);
deopt_index++;
}
#if defined(V8_TARGET_ARCH_RISCV32) || defined(V8_TARGET_ARCH_RISCV64)
__ EndBlockPools();
#endif
return true;
}
void MaglevCodeGenerator::EmitExceptionHandlerTrampolines() {
if (code_gen_state_.handlers().empty()) return;
__ RecordComment("-- Exception handler trampolines");
#ifdef DEBUG
masm()->set_allow_allocate(true);
#endif
for (NodeBase* node : code_gen_state_.handlers()) {
DCHECK(node->properties().can_throw());
DCHECK(node->properties().can_allocate());
ExceptionHandlerTrampolineBuilder::Build(masm(), node);
}
#ifdef DEBUG
masm()->set_allow_allocate(false);
#endif
}
void MaglevCodeGenerator::EmitMetadata() {
masm()->Align(InstructionStream::kMetadataAlignment);
safepoint_table_builder_.Emit(masm(), stack_slot_count_with_fixed_frame());
handler_table_offset_ = HandlerTable::EmitReturnTableStart(masm());
for (NodeBase* node : code_gen_state_.handlers()) {
ExceptionHandlerInfo* info = node->exception_handler_info();
DCHECK_IMPLIES(info->ShouldLazyDeopt(),
!info->trampoline_entry().is_bound());
int pos = info->ShouldLazyDeopt() ? HandlerTable::kLazyDeopt
: info->trampoline_entry().pos();
HandlerTable::EmitReturnEntry(masm(), info->pc_offset(), pos);
}
}
MaybeHandle<Code> MaglevCodeGenerator::BuildCodeObject(
LocalIsolate* local_isolate) {
if (!code_gen_succeeded_) return {};
Handle<TrustedByteArray> source_positions =
source_position_table_builder_.ToSourcePositionTable(local_isolate);
Handle<DeoptimizationData> deopt_data =
(v8_flags.maglev_deopt_data_on_background &&
!v8_flags.maglev_build_code_on_background)
? deopt_data_
: GenerateDeoptimizationData(local_isolate);
CHECK(!deopt_data.is_null());
CodeDesc desc;
masm()->GetCode(local_isolate, &desc, &safepoint_table_builder_,
handler_table_offset_);
auto builder =
Factory::CodeBuilder{local_isolate, desc, CodeKind::MAGLEV}
.set_stack_slots(stack_slot_count_with_fixed_frame())
.set_parameter_count(parameter_count())
.set_deoptimization_data(deopt_data)
.set_source_position_table(source_positions)
.set_inlined_bytecode_size(
graph_->total_inlined_bytecode_size() +
graph_->total_inlined_bytecode_size_small())
.set_osr_offset(
code_gen_state_.compilation_info()->toplevel_osr_offset());
if (is_context_specialized_) {
builder.set_is_context_specialized();
}
return builder.TryBuild();
}
GlobalHandleVector<Map> MaglevCodeGenerator::CollectRetainedMaps(
DirectHandle<Code> code) {
DCHECK(code->is_optimized_code());
DisallowGarbageCollection no_gc;
GlobalHandleVector<Map> maps(local_isolate_->heap());
PtrComprCageBase cage_base(local_isolate_);
int const mode_mask = RelocInfo::EmbeddedObjectModeMask();
for (RelocIterator it(*code, mode_mask); !it.done(); it.next()) {
DCHECK(RelocInfo::IsEmbeddedObjectMode(it.rinfo()->rmode()));
Tagged<HeapObject> target_object = it.rinfo()->target_object(cage_base);
if (code->IsWeakObjectInOptimizedCode(target_object)) {
if (IsMap(target_object, cage_base)) {
maps.Push(Cast<Map>(target_object));
}
}
}
return maps;
}
Handle<DeoptimizationData> MaglevCodeGenerator::GenerateDeoptimizationData(
LocalIsolate* local_isolate) {
int eager_deopt_count =
static_cast<int>(code_gen_state_.eager_deopts().size());
int lazy_deopt_count = static_cast<int>(code_gen_state_.lazy_deopts().size());
int deopt_count = lazy_deopt_count + eager_deopt_count;
if (deopt_count == 0 && !graph_->is_osr()) {
return DeoptimizationData::Empty(local_isolate);
}
Handle<DeoptimizationData> data =
DeoptimizationData::New(local_isolate, deopt_count);
DirectHandle<DeoptimizationFrameTranslation> translations =
frame_translation_builder_.ToFrameTranslation(local_isolate->factory());
DirectHandle<SharedFunctionInfoWrapper> sfi_wrapper =
local_isolate->factory()->NewSharedFunctionInfoWrapper(
code_gen_state_.compilation_info()
->toplevel_compilation_unit()
->shared_function_info()
.object());
{
DisallowGarbageCollection no_gc;
Tagged<DeoptimizationData> raw_data = *data;
raw_data->SetFrameTranslation(*translations);
raw_data->SetInlinedFunctionCount(Smi::FromInt(inlined_function_count_));
raw_data->SetOptimizationId(
Smi::FromInt(local_isolate->NextOptimizationId()));
DCHECK_NE(deopt_exit_start_offset_, -1);
raw_data->SetDeoptExitStart(Smi::FromInt(deopt_exit_start_offset_));
raw_data->SetEagerDeoptCount(Smi::FromInt(eager_deopt_count));
raw_data->SetLazyDeoptCount(Smi::FromInt(lazy_deopt_count));
raw_data->SetWrappedSharedFunctionInfo(*sfi_wrapper);
}
int inlined_functions_size =
static_cast<int>(graph_->inlined_functions().size());
DirectHandle<ProtectedDeoptimizationLiteralArray> protected_literals =
local_isolate->factory()->NewProtectedFixedArray(
static_cast<uint32_t>(protected_deopt_literals_vector_.size()));
DirectHandle<DeoptimizationLiteralArray> literals =
local_isolate->factory()->NewDeoptimizationLiteralArray(
static_cast<uint32_t>(deopt_literals_vector_.size()));
DirectHandle<TrustedPodArray<InliningPosition>> inlining_positions =
TrustedPodArray<InliningPosition>::New(local_isolate,
inlined_functions_size);
DisallowGarbageCollection no_gc;
Tagged<ProtectedDeoptimizationLiteralArray> raw_protected_literals =
*protected_literals;
for (size_t i = 0; i < protected_deopt_literals_vector_.size(); ++i) {
raw_protected_literals->set(static_cast<int>(i),
*protected_deopt_literals_vector_[i]);
}
Tagged<DeoptimizationLiteralArray> raw_literals = *literals;
for (size_t i = 0; i < deopt_literals_vector_.size(); ++i) {
raw_literals->set(static_cast<int>(i), *deopt_literals_vector_[i]);
}
for (int i = 0; i < inlined_functions_size; i++) {
auto inlined_function_info = graph_->inlined_functions()[i];
inlining_positions->set(i, inlined_function_info.position);
}
Tagged<DeoptimizationData> raw_data = *data;
raw_data->SetProtectedLiteralArray(raw_protected_literals);
raw_data->SetLiteralArray(raw_literals);
raw_data->SetInliningPositions(*inlining_positions);
auto info = code_gen_state_.compilation_info();
raw_data->SetOsrBytecodeOffset(
Smi::FromInt(info->toplevel_osr_offset().ToInt()));
if (graph_->is_osr()) {
raw_data->SetOsrPcOffset(Smi::FromInt(code_gen_state_.osr_entry()->pos()));
} else {
raw_data->SetOsrPcOffset(Smi::FromInt(-1));
}
int i = 0;
for (EagerDeoptInfo* deopt_info : code_gen_state_.eager_deopts()) {
DCHECK_NE(deopt_info->translation_index(), -1);
raw_data->SetBytecodeOffset(i, deopt_info->top_frame().GetBytecodeOffset());
raw_data->SetTranslationIndex(
i, Smi::FromInt(deopt_info->translation_index()));
raw_data->SetPc(i, Smi::FromInt(deopt_info->deopt_entry_label()->pos()));
#ifdef DEBUG
raw_data->SetNodeId(i, Smi::FromInt(i));
#endif
i++;
}
for (LazyDeoptInfo* deopt_info : code_gen_state_.lazy_deopts()) {
DCHECK_NE(deopt_info->translation_index(), -1);
raw_data->SetBytecodeOffset(i, deopt_info->top_frame().GetBytecodeOffset());
raw_data->SetTranslationIndex(
i, Smi::FromInt(deopt_info->translation_index()));
raw_data->SetPc(i, Smi::FromInt(deopt_info->deopt_entry_label()->pos()));
#ifdef DEBUG
raw_data->SetNodeId(i, Smi::FromInt(i));
#endif
i++;
}
#ifdef DEBUG
raw_data->Verify(code_gen_state_.compilation_info()
->toplevel_compilation_unit()
->bytecode()
.object());
#endif
return data;
}
}
}
}