%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/compiler/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/compiler/wasm-inlining.cc |
// Copyright 2021 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. #include "src/compiler/wasm-inlining.h" #include <cinttypes> #include "src/compiler/all-nodes.h" #include "src/compiler/compiler-source-position-table.h" #include "src/compiler/node-matchers.h" #include "src/compiler/wasm-compiler.h" #include "src/wasm/function-body-decoder.h" #include "src/wasm/graph-builder-interface.h" #include "src/wasm/wasm-features.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-subtyping.h" namespace v8 { namespace internal { namespace compiler { Reduction WasmInliner::Reduce(Node* node) { switch (node->opcode()) { case IrOpcode::kCall: case IrOpcode::kTailCall: return ReduceCall(node); default: return NoChange(); } } #define TRACE(...) \ if (v8_flags.trace_wasm_inlining) PrintF(__VA_ARGS__) void WasmInliner::Trace(Node* call, int inlinee, const char* decision) { TRACE("[function %d: considering node %d, call to %d: %s]\n", data_.func_index, call->id(), inlinee, decision); } int WasmInliner::GetCallCount(Node* call) { if (!env_->enabled_features.has_inlining() && !env_->module->is_wasm_gc) { return 0; } return mcgraph()->GetCallCount(call->id()); } // TODO(12166): Save inlined frames for trap/--trace-wasm purposes. Reduction WasmInliner::ReduceCall(Node* call) { DCHECK(call->opcode() == IrOpcode::kCall || call->opcode() == IrOpcode::kTailCall); if (seen_.find(call) != seen_.end()) { TRACE("[function %d: have already seen node %d, skipping]\n", data_.func_index, call->id()); return NoChange(); } seen_.insert(call); Node* callee = NodeProperties::GetValueInput(call, 0); IrOpcode::Value reloc_opcode = mcgraph_->machine()->Is32() ? IrOpcode::kRelocatableInt32Constant : IrOpcode::kRelocatableInt64Constant; if (callee->opcode() != reloc_opcode) { TRACE("[function %d: node %d: not a relocatable constant]\n", data_.func_index, call->id()); return NoChange(); } auto info = OpParameter<RelocatablePtrConstantInfo>(callee->op()); uint32_t inlinee_index = static_cast<uint32_t>(info.value()); if (info.rmode() != RelocInfo::WASM_CALL) { Trace(call, inlinee_index, "not a wasm call"); return NoChange(); } if (inlinee_index < module()->num_imported_functions) { Trace(call, inlinee_index, "imported function"); return NoChange(); } // We limit the times a function can be inlined to avoid repeatedly inlining // recursive calls. Since we only check here (and not in {Finalize}), it is // possible to exceed this limit if we find a large number of calls in a // single pass. constexpr int kMaximumInlinedCallsPerFunction = 3; if (function_inlining_count_[inlinee_index] >= kMaximumInlinedCallsPerFunction) { Trace(call, inlinee_index, "too many inlined calls to (recursive?) function"); return NoChange(); } CHECK_LT(inlinee_index, module()->functions.size()); const wasm::WasmFunction* inlinee = &module()->functions[inlinee_index]; base::Vector<const uint8_t> function_bytes = data_.wire_bytes_storage->GetCode(inlinee->code); int call_count = GetCallCount(call); int wire_byte_size = static_cast<int>(function_bytes.size()); int min_count_for_inlining = wire_byte_size / 2; // If liftoff ran and collected call counts, only inline calls that have been // invoked often, except for truly tiny functions. if (v8_flags.liftoff && (env_->enabled_features.has_inlining() || env_->module->is_wasm_gc) && wire_byte_size >= 12 && call_count < min_count_for_inlining) { Trace(call, inlinee_index, "not called often enough"); return NoChange(); } Trace(call, inlinee_index, "adding to inlining candidates"); CandidateInfo candidate{call, inlinee_index, call_count, function_bytes.length()}; inlining_candidates_.push(candidate); return NoChange(); } bool SmallEnoughToInline(size_t current_graph_size, uint32_t candidate_size, size_t initial_graph_size) { if (candidate_size > v8_flags.wasm_inlining_max_size) { return false; } if (WasmInliner::graph_size_allows_inlining( current_graph_size + candidate_size, initial_graph_size)) { return true; } // For truly tiny functions, let's be a bit more generous. return candidate_size <= 12 && WasmInliner::graph_size_allows_inlining(current_graph_size - 100, initial_graph_size); } void WasmInliner::Trace(const CandidateInfo& candidate, const char* decision) { TRACE( " [function %d: considering candidate {@%d, index=%d, count=%d, " "size=%d, score=%" PRId64 "}: %s]\n", data_.func_index, candidate.node->id(), candidate.inlinee_index, candidate.call_count, candidate.wire_byte_size, candidate.score(), decision); } void WasmInliner::Finalize() { TRACE("[function %d (%s): %s]\n", data_.func_index, debug_name_, inlining_candidates_.empty() ? "no inlining candidates" : "going through inlining candidates"); if (inlining_candidates_.empty()) return; while (!inlining_candidates_.empty()) { CandidateInfo candidate = inlining_candidates_.top(); inlining_candidates_.pop(); Node* call = candidate.node; if (call->IsDead()) { Trace(candidate, "dead node"); continue; } // We could build the candidate's graph first and consider its node count, // but it turns out that wire byte size and node count are quite strongly // correlated, at about 1.16 nodes per wire byte (measured for J2Wasm). if (!SmallEnoughToInline(current_graph_size_, candidate.wire_byte_size, initial_graph_size_)) { Trace(candidate, "not enough inlining budget"); continue; } const wasm::WasmFunction* inlinee = &module()->functions[candidate.inlinee_index]; DCHECK_EQ(inlinee->sig->parameter_count(), call->op()->ValueInputCount() - 2); #if DEBUG // The two first parameters in the call are the function and instance, and // then come the wasm function parameters. for (uint32_t i = 0; i < inlinee->sig->parameter_count(); i++) { if (!NodeProperties::IsTyped(call->InputAt(i + 2))) continue; wasm::TypeInModule param_type = NodeProperties::GetType(call->InputAt(i + 2)).AsWasm(); CHECK(IsSubtypeOf(param_type.type, inlinee->sig->GetParam(i), param_type.module, module())); } #endif base::Vector<const uint8_t> function_bytes = data_.wire_bytes_storage->GetCode(inlinee->code); const wasm::FunctionBody inlinee_body{inlinee->sig, inlinee->code.offset(), function_bytes.begin(), function_bytes.end()}; // If the inlinee was not validated before, do that now. if (V8_UNLIKELY( !module()->function_was_validated(candidate.inlinee_index))) { if (ValidateFunctionBody(env_->enabled_features, module(), detected_, inlinee_body) .failed()) { Trace(candidate, "function is invalid"); // At this point we cannot easily raise a compilation error any more. // Since this situation is highly unlikely though, we just ignore this // inlinee and move on. The same validation error will be triggered // again when actually compiling the invalid function. continue; } module()->set_function_validated(candidate.inlinee_index); } std::vector<WasmLoopInfo> inlinee_loop_infos; wasm::DanglingExceptions dangling_exceptions; size_t subgraph_min_node_id = graph()->NodeCount(); Node* inlinee_start; Node* inlinee_end; SourcePosition caller_pos = data_.source_positions->GetSourcePosition(candidate.node); inlining_positions_->push_back( {static_cast<int>(candidate.inlinee_index), caller_pos}); int inlining_position_id = static_cast<int>(inlining_positions_->size()) - 1; WasmGraphBuilder builder(env_, zone(), mcgraph_, inlinee_body.sig, data_.source_positions); builder.set_inlining_id(inlining_position_id); { Graph::SubgraphScope scope(graph()); wasm::BuildTFGraph(zone()->allocator(), env_->enabled_features, module(), &builder, detected_, inlinee_body, &inlinee_loop_infos, &dangling_exceptions, data_.node_origins, candidate.inlinee_index, data_.assumptions, NodeProperties::IsExceptionalCall(call) ? wasm::kInlinedHandledCall : wasm::kInlinedNonHandledCall); inlinee_start = graph()->start(); inlinee_end = graph()->end(); } size_t additional_nodes = graph()->NodeCount() - subgraph_min_node_id; Trace(candidate, "inlining"); current_graph_size_ += additional_nodes; DCHECK_GE(function_inlining_count_[candidate.inlinee_index], 0); function_inlining_count_[candidate.inlinee_index]++; if (call->opcode() == IrOpcode::kCall) { InlineCall(call, inlinee_start, inlinee_end, inlinee->sig, &dangling_exceptions); } else { InlineTailCall(call, inlinee_start, inlinee_end); } call->Kill(); data_.loop_infos->insert(data_.loop_infos->end(), inlinee_loop_infos.begin(), inlinee_loop_infos.end()); // Returning after inlining, so that new calls in the inlined body are added // to the candidates list and prioritized if they have a higher score. return; } } /* Rewire callee formal parameters to the call-site real parameters. Rewire * effect and control dependencies of callee's start node with the respective * inputs of the call node. */ void WasmInliner::RewireFunctionEntry(Node* call, Node* callee_start) { Node* control = NodeProperties::GetControlInput(call); Node* effect = NodeProperties::GetEffectInput(call); for (Edge edge : callee_start->use_edges()) { Node* use = edge.from(); switch (use->opcode()) { case IrOpcode::kParameter: { // Index 0 is the callee node. int index = 1 + ParameterIndexOf(use->op()); Replace(use, NodeProperties::GetValueInput(call, index)); break; } default: if (NodeProperties::IsEffectEdge(edge)) { edge.UpdateTo(effect); } else if (NodeProperties::IsControlEdge(edge)) { // Projections pointing to the inlinee start are floating control. // They should point to the graph's start. edge.UpdateTo(use->opcode() == IrOpcode::kProjection ? graph()->start() : control); } else { UNREACHABLE(); } Revisit(edge.from()); break; } } } void WasmInliner::InlineTailCall(Node* call, Node* callee_start, Node* callee_end) { DCHECK_EQ(call->opcode(), IrOpcode::kTailCall); // 1) Rewire function entry. RewireFunctionEntry(call, callee_start); // 2) For tail calls, all we have to do is rewire all terminators of the // inlined graph to the end of the caller graph. for (Node* const input : callee_end->inputs()) { DCHECK(IrOpcode::IsGraphTerminator(input->opcode())); MergeControlToEnd(graph(), common(), input); } for (Edge edge_to_end : call->use_edges()) { DCHECK_EQ(edge_to_end.from(), graph()->end()); edge_to_end.UpdateTo(mcgraph()->Dead()); } callee_end->Kill(); call->Kill(); Revisit(graph()->end()); } void WasmInliner::InlineCall(Node* call, Node* callee_start, Node* callee_end, const wasm::FunctionSig* inlinee_sig, wasm::DanglingExceptions* dangling_exceptions) { DCHECK_EQ(call->opcode(), IrOpcode::kCall); Node* handler = nullptr; bool is_exceptional_call = NodeProperties::IsExceptionalCall(call, &handler); // 1) Rewire function entry. RewireFunctionEntry(call, callee_start); // 2) Handle all graph terminators for the callee. NodeVector return_nodes(zone()); for (Node* const input : callee_end->inputs()) { DCHECK(IrOpcode::IsGraphTerminator(input->opcode())); switch (input->opcode()) { case IrOpcode::kReturn: // Returns are collected to be rewired into the caller graph later. return_nodes.push_back(input); break; case IrOpcode::kDeoptimize: case IrOpcode::kTerminate: case IrOpcode::kThrow: MergeControlToEnd(graph(), common(), input); break; case IrOpcode::kTailCall: { // A tail call in the callee inlined in a regular call in the caller has // to be transformed into a regular call, and then returned from the // inlinee. It will then be handled like any other return. auto descriptor = CallDescriptorOf(input->op()); NodeProperties::ChangeOp(input, common()->Call(descriptor)); DCHECK_GT(input->op()->EffectOutputCount(), 0); DCHECK_GT(input->op()->ControlOutputCount(), 0); Node* effect = input; Node* control = input; if (is_exceptional_call) { // Remember dangling exception (will be connected later). Node* if_exception = graph()->NewNode( mcgraph()->common()->IfException(), input, control); dangling_exceptions->Add(if_exception, if_exception, if_exception); control = graph()->NewNode(mcgraph()->common()->IfSuccess(), input); } int return_arity = static_cast<int>(inlinee_sig->return_count()); NodeVector return_inputs(zone()); // The first input of a return node is always the 0 constant. return_inputs.push_back(graph()->NewNode(common()->Int32Constant(0))); if (return_arity == 1) { // Tail calls are untyped; we have to type the node here. // TODO(manoskouk): Try to compute a more precise type from the callee // node. NodeProperties::SetType( input, Type::Wasm({inlinee_sig->GetReturn(0), module()}, graph()->zone())); return_inputs.push_back(input); } else if (return_arity > 1) { for (int i = 0; i < return_arity; i++) { Node* ith_projection = graph()->NewNode(common()->Projection(i), input, control); // Similarly here we have to type the call's projections. NodeProperties::SetType( ith_projection, Type::Wasm({inlinee_sig->GetReturn(i), module()}, graph()->zone())); return_inputs.push_back(ith_projection); } } // Add effect and control inputs. return_inputs.push_back(effect); return_inputs.push_back(control); Node* ret = graph()->NewNode(common()->Return(return_arity), static_cast<int>(return_inputs.size()), return_inputs.data()); return_nodes.push_back(ret); break; } default: UNREACHABLE(); } } callee_end->Kill(); // 3) Rewire unhandled calls to the handler. if (is_exceptional_call) { int handler_count = static_cast<int>(dangling_exceptions->Size()); if (handler_count > 0) { Node* control_output = graph()->NewNode(common()->Merge(handler_count), handler_count, dangling_exceptions->controls.data()); std::vector<Node*>& effects(dangling_exceptions->effects); std::vector<Node*>& values(dangling_exceptions->exception_values); effects.push_back(control_output); values.push_back(control_output); Node* value_output = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, handler_count), handler_count + 1, values.data()); Node* effect_output = graph()->NewNode(common()->EffectPhi(handler_count), handler_count + 1, effects.data()); ReplaceWithValue(handler, value_output, effect_output, control_output); } else { // Nothing in the inlined function can throw. Remove the handler. ReplaceWithValue(handler, mcgraph()->Dead(), mcgraph()->Dead(), mcgraph()->Dead()); } } if (return_nodes.size() > 0) { /* 4) Collect all return site value, effect, and control inputs into phis * and merges. */ int const return_count = static_cast<int>(return_nodes.size()); NodeVector controls(zone()); NodeVector effects(zone()); for (Node* const return_node : return_nodes) { controls.push_back(NodeProperties::GetControlInput(return_node)); effects.push_back(NodeProperties::GetEffectInput(return_node)); } Node* control_output = graph()->NewNode(common()->Merge(return_count), return_count, &controls.front()); effects.push_back(control_output); Node* effect_output = graph()->NewNode(common()->EffectPhi(return_count), static_cast<int>(effects.size()), &effects.front()); // The first input of a return node is discarded. This is because Wasm // functions always return an additional 0 constant as a first return value. DCHECK( Int32Matcher(NodeProperties::GetValueInput(return_nodes[0], 0)).Is(0)); int const return_arity = return_nodes[0]->op()->ValueInputCount() - 1; NodeVector values(zone()); #if DEBUG for (Node* const return_node : return_nodes) { // 3 = effect, control, first 0 return value. CHECK_EQ(return_arity, return_node->InputCount() - 3); } #endif for (int i = 0; i < return_arity; i++) { NodeVector ith_values(zone()); for (Node* const return_node : return_nodes) { Node* value = NodeProperties::GetValueInput(return_node, i + 1); ith_values.push_back(value); } ith_values.push_back(control_output); // Find the correct machine representation for the return values from the // inlinee signature. MachineRepresentation repr = inlinee_sig->GetReturn(i).machine_representation(); Node* ith_value_output = graph()->NewNode( common()->Phi(repr, return_count), static_cast<int>(ith_values.size()), &ith_values.front()); values.push_back(ith_value_output); } for (Node* return_node : return_nodes) return_node->Kill(); if (return_arity == 0) { // Void function, no value uses. ReplaceWithValue(call, mcgraph()->Dead(), effect_output, control_output); } else if (return_arity == 1) { // One return value. Just replace value uses of the call node with it. // Note: This will automatically detect and replace the IfSuccess node // correctly. ReplaceWithValue(call, values[0], effect_output, control_output); } else { // Multiple returns. We have to find the projections of the call node and // replace them with the returned values. for (Edge use_edge : call->use_edges()) { if (NodeProperties::IsValueEdge(use_edge)) { Node* use = use_edge.from(); DCHECK_EQ(use->opcode(), IrOpcode::kProjection); ReplaceWithValue(use, values[ProjectionIndexOf(use->op())]); } } // All value inputs are replaced by the above loop, so it is ok to use // Dead() as a dummy for value replacement. ReplaceWithValue(call, mcgraph()->Dead(), effect_output, control_output); } } else { // The callee can never return. The call node and all its uses are dead. ReplaceWithValue(call, mcgraph()->Dead(), mcgraph()->Dead(), mcgraph()->Dead()); } } const wasm::WasmModule* WasmInliner::module() const { return env_->module; } #undef TRACE } // namespace compiler } // namespace internal } // namespace v8