blob: bf00635f75ccc029c97c2685167c429dc87f3655 [file] [log] [blame]
// Copyright 2025 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_MAGLEV_MAGLEV_INLINING_H_
#define V8_MAGLEV_MAGLEV_INLINING_H_
#include "src/base/logging.h"
#include "src/compiler/heap-refs.h"
#include "src/compiler/js-heap-broker.h"
#include "src/execution/local-isolate.h"
#include "src/maglev/maglev-basic-block.h"
#include "src/maglev/maglev-compilation-info.h"
#include "src/maglev/maglev-compilation-unit.h"
#include "src/maglev/maglev-deopt-frame-visitor.h"
#include "src/maglev/maglev-graph-builder.h"
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-ir.h"
#include "src/objects/shared-function-info.h"
namespace v8::internal::maglev {
class UpdateInputsProcessor {
public:
explicit UpdateInputsProcessor(ValueNode* from, ValueNode* to)
: from_(from), to_(to) {}
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) {}
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
template <typename NodeT>
ProcessResult Process(NodeT* node, const ProcessingState& state) {
for (Input& input : *node) {
if (input.node() == from_) {
input.set_node(to_);
to_->add_use();
}
}
if constexpr (NodeT::kProperties.can_eager_deopt()) {
node->eager_deopt_info()->ForEachInput([&](ValueNode*& node) {
if (node == from_) {
node = to_;
to_->add_use();
}
});
}
if constexpr (NodeT::kProperties.can_lazy_deopt()) {
node->lazy_deopt_info()->ForEachInput([&](ValueNode*& node) {
if (node == from_) {
node = to_;
to_->add_use();
}
});
}
return ProcessResult::kContinue;
}
private:
ValueNode* from_;
ValueNode* to_;
};
class MaglevInliner {
public:
MaglevInliner(MaglevCompilationInfo* compilation_info, Graph* graph)
: compilation_info_(compilation_info), graph_(graph) {}
void Run(bool is_tracing_maglev_graphs_enabled) {
// TODO(victorgomes): Add some heuristics to choose which function to
// inline.
while (!graph_->inlineable_calls().empty()) {
if (graph_->total_inlined_bytecode_size() >
v8_flags.max_maglev_inlined_bytecode_size_cumulative) {
// No more inlining.
break;
}
MaglevCallSiteInfo* call_site = graph_->inlineable_calls().back();
graph_->inlineable_calls().pop_back();
ValueNode* result = BuildInlineFunction(call_site);
if (result) {
if (auto alloc = result->TryCast<InlinedAllocation>()) {
// TODO(victorgomes): Support eliding VOs.
alloc->ForceEscaping();
#ifdef DEBUG
alloc->set_is_returned_value_from_inline_call();
#endif // DEBUG
}
GraphProcessor<UpdateInputsProcessor> substitute_use(
call_site->generic_call_node, result);
substitute_use.ProcessGraph(graph_);
}
// If --trace-maglev-inlining-verbose, we print the graph after each
// inlining step/call.
if (is_tracing_maglev_graphs_enabled && v8_flags.print_maglev_graphs &&
v8_flags.trace_maglev_inlining_verbose) {
std::cout << "\nAfter inlining "
<< call_site->generic_call_node->shared_function_info()
<< std::endl;
PrintGraph(std::cout, compilation_info_, graph_);
}
}
// Otherwise we print just once at the end.
if (is_tracing_maglev_graphs_enabled && v8_flags.print_maglev_graphs &&
!v8_flags.trace_maglev_inlining_verbose) {
std::cout << "\nAfter inlining" << std::endl;
PrintGraph(std::cout, compilation_info_, graph_);
}
}
private:
MaglevCompilationInfo* compilation_info_;
Graph* graph_;
compiler::JSHeapBroker* broker() const { return compilation_info_->broker(); }
Zone* zone() const { return compilation_info_->zone(); }
ValueNode* BuildInlineFunction(MaglevCallSiteInfo* call_site) {
CallKnownJSFunction* call_node = call_site->generic_call_node;
MaglevCallerDetails* caller_details = &call_site->caller_details;
DeoptFrame* caller_deopt_frame = caller_details->deopt_frame;
const MaglevCompilationUnit* caller_unit =
&caller_deopt_frame->GetCompilationUnit();
compiler::SharedFunctionInfoRef shared = call_node->shared_function_info();
if (v8_flags.trace_maglev_inlining) {
std::cout << " non-eager inlining " << shared << std::endl;
}
// Create a new compilation unit.
MaglevCompilationUnit* inner_unit = MaglevCompilationUnit::NewInner(
zone(), caller_unit, shared, call_site->feedback_cell);
compiler::BytecodeArrayRef bytecode = shared.GetBytecodeArray(broker());
graph_->add_inlined_bytecode_size(bytecode.length());
// Create a new graph builder for the inlined function.
LocalIsolate* local_isolate = broker()->local_isolate_or_isolate();
MaglevGraphBuilder inner_graph_builder(local_isolate, inner_unit, graph_,
&call_site->caller_details);
// Update caller deopt frame with inlined arguments.
caller_details->deopt_frame =
inner_graph_builder.AddInlinedArgumentsToDeoptFrame(
caller_deopt_frame, inner_unit, call_node->closure().node(),
call_site->caller_details.arguments);
BasicBlock* call_block = call_node->owner();
// We truncate the graph to build the function in-place, preserving the
// invariant that all jumps move forward (except JumpLoop).
std::vector<BasicBlock*> saved_bb = TruncateGraphAt(call_block);
// Truncate the basic block and remove the generic call node.
ControlNode* control_node = call_block->reset_control_node();
ZoneVector<Node*> rem_nodes_in_call_block =
call_block->Split(call_node, zone());
// Set the inner graph builder to build in the truncated call block.
inner_graph_builder.set_current_block(call_block);
ReduceResult result = inner_graph_builder.BuildInlineFunction(
caller_deopt_frame->GetSourcePosition(), call_node->context().node(),
call_node->closure().node(), call_node->new_target().node());
if (result.IsDoneWithAbort()) {
// Restore the rest of the graph.
for (auto bb : saved_bb) {
graph_->Add(bb);
}
RemovePredecessorFollowing(control_node, call_block);
// TODO(victorgomes): We probably don't need to iterate all the graph to
// remove unreachable blocks, but only the successors of control_node in
// saved_bbs.
RemoveUnreachableBlocks();
return nullptr;
}
DCHECK(result.IsDoneWithValue());
ValueNode* returned_value =
EnsureTagged(inner_graph_builder, result.value());
// Resume execution using the final block of the inner builder.
// Add remaining nodes to the final block and use the control flow of the
// old call block.
BasicBlock* final_block = inner_graph_builder.FinishInlinedBlockForCaller(
control_node, rem_nodes_in_call_block);
DCHECK_NOT_NULL(final_block);
// Update the predecessor of the successors of the {final_block}, that were
// previously pointing to {call_block}.
final_block->ForEachSuccessor(
[call_block, final_block](BasicBlock* successor) {
UpdatePredecessorsOf(successor, call_block, final_block);
});
// Restore the rest of the graph.
for (auto bb : saved_bb) {
graph_->Add(bb);
}
return returned_value;
}
// Truncates the graph at the given basic block `block`. All blocks
// following `block` (exclusive) are removed from the graph and returned.
// `block` itself is removed from the graph and not returned.
std::vector<BasicBlock*> TruncateGraphAt(BasicBlock* block) {
// TODO(victorgomes): Consider using a linked list of basic blocks in Maglev
// instead of a vector.
auto it =
std::find(graph_->blocks().begin(), graph_->blocks().end(), block);
CHECK_NE(it, graph_->blocks().end());
size_t index = std::distance(graph_->blocks().begin(), it);
std::vector<BasicBlock*> saved_bb(graph_->blocks().begin() + index + 1,
graph_->blocks().end());
graph_->blocks().resize(index);
return saved_bb;
}
template <class Node, typename... Args>
ValueNode* AddNodeAtBlockEnd(MaglevGraphBuilder& builder,
std::initializer_list<ValueNode*> inputs,
Args&&... args) {
ValueNode* node =
NodeBase::New<Node>(zone(), inputs, std::forward<Args>(args)...);
DCHECK(!node->properties().can_eager_deopt());
DCHECK(!node->properties().can_lazy_deopt());
builder.node_buffer().push_back(node);
RegisterNode(builder, node);
return node;
}
void RegisterNode(MaglevGraphBuilder& builder, Node* node) {
if (builder.has_graph_labeller()) {
builder.graph_labeller()->RegisterNode(node);
}
}
ValueNode* EnsureTagged(MaglevGraphBuilder& builder, ValueNode* node) {
// TODO(victorgomes): Use KNA to create better conversion nodes?
switch (node->value_representation()) {
case ValueRepresentation::kInt32:
return AddNodeAtBlockEnd<Int32ToNumber>(builder, {node});
case ValueRepresentation::kUint32:
return AddNodeAtBlockEnd<Uint32ToNumber>(builder, {node});
case ValueRepresentation::kFloat64:
return AddNodeAtBlockEnd<Float64ToTagged>(
builder, {node}, Float64ToTagged::ConversionMode::kForceHeapNumber);
case ValueRepresentation::kHoleyFloat64:
return AddNodeAtBlockEnd<HoleyFloat64ToTagged>(
builder, {node},
HoleyFloat64ToTagged::ConversionMode::kForceHeapNumber);
case ValueRepresentation::kIntPtr:
return AddNodeAtBlockEnd<IntPtrToNumber>(builder, {node});
case ValueRepresentation::kTagged:
return node;
}
}
static void UpdatePredecessorsOf(BasicBlock* block, BasicBlock* prev_pred,
BasicBlock* new_pred) {
if (!block->has_state()) {
DCHECK_EQ(block->predecessor(), prev_pred);
block->set_predecessor(new_pred);
return;
}
for (int i = 0; i < block->predecessor_count(); i++) {
if (block->predecessor_at(i) == prev_pred) {
block->state()->set_predecessor_at(i, new_pred);
break;
}
}
}
void RemovePredecessorFollowing(ControlNode* control,
BasicBlock* call_block) {
BasicBlock::ForEachSuccessorFollowing(control, [&](BasicBlock* succ) {
if (!succ->has_state()) return;
if (succ->is_loop() && succ->backedge_predecessor() == call_block) {
succ->state()->TurnLoopIntoRegularBlock();
return;
}
for (int i = succ->predecessor_count() - 1; i >= 0; i--) {
if (succ->predecessor_at(i) == call_block) {
succ->state()->RemovePredecessorAt(i);
}
}
});
}
void RemoveUnreachableBlocks() {
absl::flat_hash_set<BasicBlock*> reachable_blocks;
absl::flat_hash_set<BasicBlock*> loop_headers_unreachable_by_backegde;
std::vector<BasicBlock*> worklist;
DCHECK(!graph_->blocks().empty());
BasicBlock* initial_bb = graph_->blocks().front();
worklist.push_back(initial_bb);
reachable_blocks.insert(initial_bb);
DCHECK(!initial_bb->is_loop());
// Add all exception handler blocks to the worklist.
// TODO(victorgomes): A catch block could still be unreachable, if no
// bbs in its try-block are unreachables, or its nodes cannot throw.
for (BasicBlock* bb : graph_->blocks()) {
if (bb->is_exception_handler_block()) {
worklist.push_back(bb);
reachable_blocks.insert(bb);
}
}
while (!worklist.empty()) {
BasicBlock* current = worklist.back();
worklist.pop_back();
if (current->is_loop()) {
loop_headers_unreachable_by_backegde.insert(current);
}
current->ForEachSuccessor([&](BasicBlock* succ) {
if (reachable_blocks.contains(succ)) {
// We have already added this block to the worklist, check only if
// that's a reachable loop header.
if (succ->is_loop()) {
// This must be the loop back edge.
DCHECK(succ->is_loop());
DCHECK_EQ(succ->backedge_predecessor(), current);
DCHECK(loop_headers_unreachable_by_backegde.contains(succ));
loop_headers_unreachable_by_backegde.erase(succ);
}
} else {
reachable_blocks.insert(succ);
worklist.push_back(succ);
}
});
}
for (BasicBlock* bb : loop_headers_unreachable_by_backegde) {
DCHECK(bb->has_state());
bb->state()->TurnLoopIntoRegularBlock();
}
ZoneVector<BasicBlock*> new_blocks(zone());
for (BasicBlock* bb : graph_->blocks()) {
if (reachable_blocks.contains(bb)) {
new_blocks.push_back(bb);
// Remove unreachable predecessors.
// If block doesn't have a merge state, it has only one predecessor, so
// it must be a reachable one.
if (!bb->has_state()) continue;
for (int i = bb->predecessor_count() - 1; i >= 0; i--) {
if (!reachable_blocks.contains(bb->predecessor_at(i))) {
bb->state()->RemovePredecessorAt(i);
}
}
}
}
graph_->set_blocks(new_blocks);
}
};
} // namespace v8::internal::maglev
#endif // V8_MAGLEV_MAGLEV_INLINING_H_