Commit ab4cf929 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm][turbofan] Set up basic inlining infrastructure

We introduce basic wasm inlining infrastructure behind a flag. The
implementation is currently incomplete. Additionally, we always inline
the function at index 0; proper inlining heuristics will be added later.

Changes:
- Rename WasmInliningPhase -> JSWasmInliningPhase
- Introduce WasmInliningPhase and WasmInliner.
- Pass additional parameters as needed to GenerateCodeForWasmFunction.
- Remove EnsureEnd in WasmGraphAssembler. Create end node at the start
  of compilation.
- Add a simple test.

Bug: v8:12166
Change-Id: Ifd7006ba378e9f74cd248b71e16869fbbb8a82be
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3141575
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76689}
parent fa0cb020
......@@ -2137,6 +2137,7 @@ filegroup(
"src/asmjs/asm-types.h",
"src/compiler/int64-lowering.h",
"src/compiler/wasm-compiler.h",
"src/compiler/wasm-inlining.h",
"src/debug/debug-wasm-objects.cc",
"src/debug/debug-wasm-objects.h",
"src/debug/debug-wasm-objects-inl.h",
......@@ -2519,6 +2520,7 @@ filegroup(
":is_v8_enable_webassembly": [
"src/compiler/int64-lowering.cc",
"src/compiler/wasm-compiler.cc",
"src/compiler/wasm-inlining.cc",
],
"//conditions:default": [],
}),
......
......@@ -3240,6 +3240,7 @@ v8_header_set("v8_internal_headers") {
"src/asmjs/asm-types.h",
"src/compiler/int64-lowering.h",
"src/compiler/wasm-compiler.h",
"src/compiler/wasm-inlining.h",
"src/debug/debug-wasm-objects-inl.h",
"src/debug/debug-wasm-objects.h",
"src/trap-handler/trap-handler-internal.h",
......@@ -3706,6 +3707,7 @@ if (v8_enable_webassembly) {
v8_compiler_sources += [
"src/compiler/int64-lowering.cc",
"src/compiler/wasm-compiler.cc",
"src/compiler/wasm-inlining.cc",
]
}
......
......@@ -96,6 +96,7 @@
#if V8_ENABLE_WEBASSEMBLY
#include "src/compiler/wasm-compiler.h"
#include "src/compiler/wasm-inlining.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/wasm-engine.h"
......@@ -1409,8 +1410,8 @@ struct InliningPhase {
};
#if V8_ENABLE_WEBASSEMBLY
struct WasmInliningPhase {
DECL_PIPELINE_PHASE_CONSTANTS(WasmInlining)
struct JSWasmInliningPhase {
DECL_PIPELINE_PHASE_CONSTANTS(JSWasmInlining)
void Run(PipelineData* data, Zone* temp_zone) {
DCHECK(data->has_js_wasm_calls());
......@@ -1684,6 +1685,25 @@ struct WasmLoopUnrollingPhase {
}
}
};
struct WasmInliningPhase {
DECL_PIPELINE_PHASE_CONSTANTS(WasmInlining)
void Run(PipelineData* data, Zone* temp_zone, wasm::CompilationEnv* env,
const wasm::WireBytesStorage* wire_bytes) {
GraphReducer graph_reducer(
temp_zone, data->graph(), &data->info()->tick_counter(), data->broker(),
data->jsgraph()->Dead(), data->observe_node_manager());
DeadCodeElimination dead(&graph_reducer, data->graph(),
data->mcgraph()->common(), temp_zone);
WasmInliner inliner(&graph_reducer, env, data->source_positions(),
data->node_origins(), data->mcgraph(), wire_bytes, 0);
AddReducer(data, &graph_reducer, &dead);
AddReducer(data, &graph_reducer, &inliner);
graph_reducer.ReduceGraph();
}
};
#endif // V8_ENABLE_WEBASSEMBLY
struct LoopExitEliminationPhase {
......@@ -2735,8 +2755,8 @@ bool PipelineImpl::OptimizeGraph(Linkage* linkage) {
#if V8_ENABLE_WEBASSEMBLY
if (data->has_js_wasm_calls()) {
DCHECK(data->info()->inline_js_wasm_calls());
Run<WasmInliningPhase>();
RunPrintAndVerify(WasmInliningPhase::phase_name(), true);
Run<JSWasmInliningPhase>();
RunPrintAndVerify(JSWasmInliningPhase::phase_name(), true);
}
#endif // V8_ENABLE_WEBASSEMBLY
......@@ -2838,8 +2858,8 @@ bool PipelineImpl::OptimizeGraphForMidTier(Linkage* linkage) {
#if V8_ENABLE_WEBASSEMBLY
if (data->has_js_wasm_calls()) {
DCHECK(data->info()->inline_js_wasm_calls());
Run<WasmInliningPhase>();
RunPrintAndVerify(WasmInliningPhase::phase_name(), true);
Run<JSWasmInliningPhase>();
RunPrintAndVerify(JSWasmInliningPhase::phase_name(), true);
}
#endif // V8_ENABLE_WEBASSEMBLY
......@@ -3175,7 +3195,8 @@ wasm::WasmCompilationResult Pipeline::GenerateCodeForWasmNativeStub(
// static
void Pipeline::GenerateCodeForWasmFunction(
OptimizedCompilationInfo* info, MachineGraph* mcgraph,
OptimizedCompilationInfo* info, wasm::CompilationEnv* env,
const wasm::WireBytesStorage* wire_bytes_storage, MachineGraph* mcgraph,
CallDescriptor* call_descriptor, SourcePositionTable* source_positions,
NodeOriginTable* node_origins, wasm::FunctionBody function_body,
const wasm::WasmModule* module, int function_index,
......@@ -3205,6 +3226,10 @@ void Pipeline::GenerateCodeForWasmFunction(
pipeline.Run<WasmLoopUnrollingPhase>(loop_info);
pipeline.RunPrintAndVerify(WasmLoopUnrollingPhase::phase_name(), true);
}
if (FLAG_wasm_inlining) {
pipeline.Run<WasmInliningPhase>(env, wire_bytes_storage);
pipeline.RunPrintAndVerify(WasmInliningPhase::phase_name(), true);
}
const bool is_asm_js = is_asmjs_module(module);
if (FLAG_wasm_opt || is_asm_js) {
......
......@@ -23,11 +23,13 @@ class ProfileDataFromFile;
class RegisterConfiguration;
namespace wasm {
struct CompilationEnv;
struct FunctionBody;
class NativeModule;
struct WasmCompilationResult;
class WasmEngine;
struct WasmModule;
class WireBytesStorage;
} // namespace wasm
namespace compiler {
......@@ -54,7 +56,8 @@ class Pipeline : public AllStatic {
// Run the pipeline for the WebAssembly compilation info.
static void GenerateCodeForWasmFunction(
OptimizedCompilationInfo* info, MachineGraph* mcgraph,
OptimizedCompilationInfo* info, wasm::CompilationEnv* env,
const wasm::WireBytesStorage* wire_bytes_storage, MachineGraph* mcgraph,
CallDescriptor* call_descriptor, SourcePositionTable* source_positions,
NodeOriginTable* node_origins, wasm::FunctionBody function_body,
const wasm::WasmModule* module, int function_index,
......
......@@ -197,14 +197,7 @@ class WasmGraphAssembler : public GraphAssembler {
return Call(call_descriptor, call_target, args...);
}
void EnsureEnd() {
if (graph()->end() == nullptr) {
graph()->SetEnd(graph()->NewNode(mcgraph()->common()->End(0)));
}
}
void MergeControlToEnd(Node* node) {
EnsureEnd();
NodeProperties::MergeControlToEnd(graph(), mcgraph()->common(), node);
}
......@@ -213,7 +206,6 @@ class WasmGraphAssembler : public GraphAssembler {
if (FLAG_debug_code) {
auto ok = MakeLabel();
GotoIfNot(condition, &ok);
EnsureEnd();
Unreachable();
Bind(&ok);
}
......@@ -501,6 +493,8 @@ void WasmGraphBuilder::Start(unsigned params) {
gasm_->LoadFunctionDataFromJSFunction(
Param(Linkage::kJSCallClosureParamIndex, "%closure")))
: Param(wasm::kWasmInstanceParameterIndex);
graph()->SetEnd(graph()->NewNode(mcgraph()->common()->End(0)));
}
Node* WasmGraphBuilder::Param(int index, const char* debug_name) {
......@@ -5587,8 +5581,6 @@ Node* WasmGraphBuilder::ArrayNewWithRtt(uint32_t array_index,
Node* element_size = Int32Constant(element_type.element_size_bytes());
Node* end_offset =
gasm_->Int32Add(start_offset, gasm_->Int32Mul(element_size, length));
// Loops need the graph's end to have been set up.
gasm_->EnsureEnd();
gasm_->Goto(&loop, start_offset);
gasm_->Bind(&loop);
{
......@@ -7855,8 +7847,9 @@ base::Vector<const char> GetDebugName(Zone* zone, int index) {
} // namespace
wasm::WasmCompilationResult ExecuteTurbofanWasmCompilation(
wasm::CompilationEnv* env, const wasm::FunctionBody& func_body,
int func_index, Counters* counters, wasm::WasmFeatures* detected) {
wasm::CompilationEnv* env, const wasm::WireBytesStorage* wire_bytes_storage,
const wasm::FunctionBody& func_body, int func_index, Counters* counters,
wasm::WasmFeatures* detected) {
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.CompileTopTier", "func_index", func_index, "body_size",
func_body.end - func_body.start);
......@@ -7908,9 +7901,10 @@ wasm::WasmCompilationResult ExecuteTurbofanWasmCompilation(
call_descriptor = GetI32WasmCallDescriptorForSimd(&zone, call_descriptor);
}
Pipeline::GenerateCodeForWasmFunction(
&info, mcgraph, call_descriptor, source_positions, node_origins,
func_body, env->module, func_index, &loop_infos);
Pipeline::GenerateCodeForWasmFunction(&info, env, wire_bytes_storage, mcgraph,
call_descriptor, source_positions,
node_origins, func_body, env->module,
func_index, &loop_infos);
if (counters) {
int zone_bytes =
......
......@@ -53,13 +53,15 @@ using TFNode = compiler::Node;
using TFGraph = compiler::MachineGraph;
class WasmCode;
class WasmFeatures;
class WireBytesStorage;
enum class LoadTransformationKind : uint8_t;
} // namespace wasm
namespace compiler {
wasm::WasmCompilationResult ExecuteTurbofanWasmCompilation(
wasm::CompilationEnv*, const wasm::FunctionBody&, int func_index, Counters*,
wasm::CompilationEnv*, const wasm::WireBytesStorage* wire_bytes_storage,
const wasm::FunctionBody&, int func_index, Counters*,
wasm::WasmFeatures* detected);
// Calls to Wasm imports are handled in several different ways, depending on the
......
// 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 "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"
namespace v8 {
namespace internal {
namespace compiler {
Reduction WasmInliner::Reduce(Node* node) {
if (node->opcode() == IrOpcode::kCall) {
return ReduceCall(node);
} else {
return NoChange();
}
}
// TODO(12166): Abstract over a heuristics provider.
Reduction WasmInliner::ReduceCall(Node* call) {
Node* callee = NodeProperties::GetValueInput(call, 0);
IrOpcode::Value reloc_opcode = mcgraph_->machine()->Is32()
? IrOpcode::kRelocatableInt32Constant
: IrOpcode::kRelocatableInt64Constant;
if (callee->opcode() != reloc_opcode) return NoChange();
auto info = OpParameter<RelocatablePtrConstantInfo>(callee->op());
if (static_cast<uint32_t>(info.value()) != inlinee_index_) return NoChange();
CHECK_LT(inlinee_index_, module()->functions.size());
const wasm::WasmFunction* function = &module()->functions[inlinee_index_];
base::Vector<const byte> function_bytes =
wire_bytes_->GetCode(function->code);
const wasm::FunctionBody inlinee_body(function->sig, function->code.offset(),
function_bytes.begin(),
function_bytes.end());
wasm::WasmFeatures detected;
WasmGraphBuilder builder(env_, zone(), mcgraph_, inlinee_body.sig, spt_);
std::vector<WasmLoopInfo> infos;
wasm::DecodeResult result;
Node* inlinee_start;
Node* inlinee_end;
{
Graph::SubgraphScope scope(graph());
result = wasm::BuildTFGraph(zone()->allocator(), env_->enabled_features,
module(), &builder, &detected, inlinee_body,
&infos, node_origins_, inlinee_index_);
inlinee_start = graph()->start();
inlinee_end = graph()->end();
}
if (result.failed()) return NoChange();
return InlineCall(call, inlinee_start, inlinee_end);
}
// TODO(12166): Handle exceptions.
// TODO(12166): Test multiple/zero returns, infinite loops.
// TODO(12166): Remove stack checks and wasm tracing from inlinee.
Reduction WasmInliner::InlineCall(Node* call, Node* callee_start,
Node* callee_end) {
DCHECK_EQ(call->opcode(), IrOpcode::kCall);
/* 1) 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.
*/
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)) {
edge.UpdateTo(control);
} else {
UNREACHABLE();
}
break;
}
}
/* 2) Rewire uses of the call node to the return values of the callee. Since
* there might be multiple return nodes in the callee, we have to create Merge
* and Phi nodes for them.
*/
NodeVector return_nodes(zone());
for (Node* const input : callee_end->inputs()) {
DCHECK(IrOpcode::IsGraphTerminator(input->opcode()));
switch (input->opcode()) {
case IrOpcode::kReturn:
return_nodes.push_back(input);
break;
case IrOpcode::kDeoptimize:
case IrOpcode::kTerminate:
case IrOpcode::kThrow:
NodeProperties::MergeControlToEnd(graph(), common(), input);
Revisit(graph()->end());
break;
case IrOpcode::kTailCall:
// TODO(12166): A tail call in the inlined function has to be
// transformed into a regular call in the caller function.
UNIMPLEMENTED();
default:
UNREACHABLE();
}
}
if (return_nodes.size() > 0) {
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());
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.
const wasm::WasmFunction* function = &module()->functions[inlinee_index_];
MachineRepresentation repr =
function->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);
}
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.
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);
}
return Replace(mcgraph()->Dead());
} else {
// The callee can never return. The call node and all its uses are dead.
ReplaceWithValue(call, mcgraph()->Dead(), mcgraph()->Dead(),
mcgraph()->Dead());
return Changed(call);
}
}
const wasm::WasmModule* WasmInliner::module() const { return env_->module; }
} // namespace compiler
} // namespace internal
} // namespace v8
// 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.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_COMPILER_WASM_INLINING_H_
#define V8_COMPILER_WASM_INLINING_H_
#include "src/compiler/graph-reducer.h"
#include "src/compiler/js-graph.h"
namespace v8 {
namespace internal {
namespace wasm {
struct CompilationEnv;
struct WasmModule;
class WireBytesStorage;
} // namespace wasm
class BytecodeOffset;
class OptimizedCompilationInfo;
namespace compiler {
class NodeOriginTable;
class SourcePositionTable;
// The WasmInliner provides the core graph inlining machinery for Webassembly
// graphs. Note that this class only deals with the mechanics of how to inline
// one graph into another, heuristics that decide what and how much to inline
// are beyond its scope. As a current placeholder, only a function at specific
// given index {inlinee_index} is inlined.
class WasmInliner final : public AdvancedReducer {
public:
WasmInliner(Editor* editor, wasm::CompilationEnv* env,
SourcePositionTable* spt, NodeOriginTable* node_origins,
MachineGraph* mcgraph, const wasm::WireBytesStorage* wire_bytes,
uint32_t inlinee_index)
: AdvancedReducer(editor),
env_(env),
spt_(spt),
node_origins_(node_origins),
mcgraph_(mcgraph),
wire_bytes_(wire_bytes),
inlinee_index_(inlinee_index) {}
const char* reducer_name() const override { return "WasmInliner"; }
Reduction Reduce(Node* node) final;
private:
Zone* zone() const { return mcgraph_->zone(); }
CommonOperatorBuilder* common() const { return mcgraph_->common(); }
Graph* graph() const { return mcgraph_->graph(); }
MachineGraph* mcgraph() const { return mcgraph_; }
const wasm::WasmModule* module() const;
Reduction ReduceCall(Node* call);
Reduction InlineCall(Node* call, Node* callee_start, Node* callee_end);
wasm::CompilationEnv* const env_;
SourcePositionTable* const spt_;
NodeOriginTable* const node_origins_;
MachineGraph* const mcgraph_;
const wasm::WireBytesStorage* const wire_bytes_;
const uint32_t inlinee_index_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_WASM_INLINING_H_
......@@ -1024,6 +1024,9 @@ DEFINE_NEG_NEG_IMPLICATION(wasm_bounds_checks, wasm_enforce_bounds_checks)
DEFINE_BOOL(wasm_math_intrinsics, true,
"intrinsify some Math imports into wasm")
DEFINE_BOOL(
wasm_inlining, false,
"enable inlining of wasm functions into wasm functions (experimental)")
DEFINE_BOOL(wasm_loop_unrolling, true,
"enable loop unrolling for wasm functions")
DEFINE_BOOL(wasm_fuzzer_gen_test, false,
......
......@@ -337,6 +337,7 @@ class RuntimeCallTimer final {
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, FrameElision) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, GenericLowering) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, Inlining) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, JSWasmInlining) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, JumpThreading) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, MidTierPopulateReferenceMaps) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, MidTierRegisterAllocator) \
......
......@@ -134,7 +134,7 @@ WasmCompilationResult WasmCompilationUnit::ExecuteFunctionCompilation(
case ExecutionTier::kTurbofan:
result = compiler::ExecuteTurbofanWasmCompilation(
env, func_body, func_index_, counters, detected);
env, wire_bytes_storage, func_body, func_index_, counters, detected);
result.for_debugging = for_debugging_;
break;
}
......
// 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.
// Flags: --wasm-inlining
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// TODO(12166): Consider running tests with --trace-wasm and inspecting their
// output.
(function SimpleInliningTest() {
let builder = new WasmModuleBuilder();
// f(x) = x - 1
let callee = builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
// g(x) = f(5) + x
builder.addFunction("main", kSig_i_i)
.addBody([kExprI32Const, 5, kExprCallFunction, callee.index,
kExprLocalGet, 0, kExprI32Add])
.exportAs("main");
let instance = builder.instantiate();
assertEquals(instance.exports.main(10), 14);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment