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

[wasm-gc][turbofan] Speculative direct calls for call_ref

Behind the --wasm-inlining flag, we introduce speculative direct calls
as an alternative to invoking functions through references.
In pseudocode, call_ref(func_ref, args...) reduces to
  if (func_ref == function_reference_at(expected_index)) {
    call_direct(expected_index, args...)
  } else call_ref(func_ref, args...)
The introduced direct call can later get inlined in WasmInliningPhase.
Currently, we always speculate that the reference is the function at
index 0. Proper heuristics, based on liftoff runtime feedback, will come
later.

Bug: v8:12166, v8:7748
Change-Id: Icd1319d3091b436e71906717fd8a2662bfbb8481
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3162602
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76884}
parent f2f392fb
......@@ -3227,6 +3227,22 @@ Node* WasmGraphBuilder::BuildCallRef(uint32_t sig_index,
return call;
}
void WasmGraphBuilder::CompareToExternalFunctionAtIndex(
Node* func_ref, uint32_t function_index, Node** success_control,
Node** failure_control) {
// Since we are comparing to a function reference, it is guaranteed that
// instance->wasm_external_functions() has been initialized.
Node* external_functions = gasm_->LoadFromObject(
MachineType::TaggedPointer(), GetInstance(),
wasm::ObjectAccess::ToTagged(
WasmInstanceObject::kWasmExternalFunctionsOffset));
Node* function_ref = gasm_->LoadFixedArrayElement(
external_functions, gasm_->IntPtrConstant(function_index),
MachineType::AnyTagged());
gasm_->Branch(gasm_->WordEqual(function_ref, func_ref), success_control,
failure_control, BranchHint::kTrue);
}
Node* WasmGraphBuilder::CallRef(uint32_t sig_index, base::Vector<Node*> args,
base::Vector<Node*> rets,
WasmGraphBuilder::CheckForNull null_check,
......
......@@ -328,6 +328,9 @@ class WasmGraphBuilder {
Node* CallRef(uint32_t sig_index, base::Vector<Node*> args,
base::Vector<Node*> rets, CheckForNull null_check,
wasm::WasmCodePosition position);
void CompareToExternalFunctionAtIndex(Node* func_ref, uint32_t function_index,
Node** success_control,
Node** failure_control);
Node* ReturnCall(uint32_t index, base::Vector<Node*> args,
wasm::WasmCodePosition position);
......
......@@ -660,13 +660,93 @@ class WasmGraphBuildingInterface {
void CallRef(FullDecoder* decoder, const Value& func_ref,
const FunctionSig* sig, uint32_t sig_index, const Value args[],
Value returns[]) {
if (!FLAG_wasm_inlining) {
DoCall(decoder, kCallRef, 0, NullCheckFor(func_ref.type), func_ref.node,
sig, sig_index, args, returns);
return;
}
// Check for equality against a function at a specific index, and if
// successful, just emit a direct call.
// TODO(12166): For now, we check against function 0. Decide the index based
// on liftoff feedback.
const uint32_t expected_function_index = 0;
TFNode* success_control;
TFNode* failure_control;
builder_->CompareToExternalFunctionAtIndex(
func_ref.node, expected_function_index, &success_control,
&failure_control);
TFNode* initial_effect = effect();
builder_->SetControl(success_control);
ssa_env_->control = success_control;
Value* returns_direct =
decoder->zone()->NewArray<Value>(sig->return_count());
DoCall(decoder, kCallDirect, expected_function_index,
CheckForNull::kWithoutNullCheck, nullptr,
decoder->module_->signature(sig_index), sig_index, args,
returns_direct);
TFNode* control_direct = control();
TFNode* effect_direct = effect();
builder_->SetEffectControl(initial_effect, failure_control);
ssa_env_->effect = initial_effect;
ssa_env_->control = failure_control;
Value* returns_ref = decoder->zone()->NewArray<Value>(sig->return_count());
DoCall(decoder, kCallRef, 0, NullCheckFor(func_ref.type), func_ref.node,
sig, sig_index, args, returns);
sig, sig_index, args, returns_ref);
TFNode* control_ref = control();
TFNode* effect_ref = effect();
TFNode* control_args[] = {control_direct, control_ref};
TFNode* control = builder_->Merge(2, control_args);
TFNode* effect_args[] = {effect_direct, effect_ref, control};
TFNode* effect = builder_->EffectPhi(2, effect_args);
ssa_env_->control = control;
ssa_env_->effect = effect;
builder_->SetEffectControl(effect, control);
for (uint32_t i = 0; i < sig->return_count(); i++) {
TFNode* phi_args[] = {returns_direct[i].node, returns_ref[i].node,
control};
returns[i].node = builder_->Phi(sig->GetReturn(i), 2, phi_args);
}
}
void ReturnCallRef(FullDecoder* decoder, const Value& func_ref,
const FunctionSig* sig, uint32_t sig_index,
const Value args[]) {
if (!FLAG_wasm_inlining) {
DoReturnCall(decoder, kCallRef, 0, NullCheckFor(func_ref.type), func_ref,
sig, sig_index, args);
return;
}
// Check for equality against a function at a specific index, and if
// successful, just emit a direct call.
// TODO(12166): For now, we check against function 0. Decide the index based
// on liftoff feedback.
const uint32_t expected_function_index = 0;
TFNode* success_control;
TFNode* failure_control;
builder_->CompareToExternalFunctionAtIndex(
func_ref.node, expected_function_index, &success_control,
&failure_control);
TFNode* initial_effect = effect();
builder_->SetControl(success_control);
ssa_env_->control = success_control;
DoReturnCall(decoder, kCallDirect, 0, CheckForNull::kWithoutNullCheck,
Value{nullptr, kWasmBottom}, sig, expected_function_index,
args);
builder_->SetEffectControl(initial_effect, failure_control);
ssa_env_->effect = initial_effect;
ssa_env_->control = failure_control;
DoReturnCall(decoder, kCallRef, 0, NullCheckFor(func_ref.type), func_ref,
sig, sig_index, args);
}
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
// Flags: --wasm-inlining --no-liftoff --experimental-wasm-return-call
// Flags: --experimental-wasm-typed-funcref
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
......@@ -239,3 +240,91 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let instance = builder.instantiate();
assertEquals(20, instance.exports.main(10, 20));
})();
(function CallRefSpecSucceededTest() {
let builder = new WasmModuleBuilder();
// f(x) = x - 1
let callee = builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
let global = builder.addGlobal(wasmRefType(0), false,
WasmInitExpr.RefFunc(callee.index));
// g(x) = f(5) + x
builder.addFunction("main", kSig_i_i)
.addBody([kExprI32Const, 5, kExprGlobalGet, global.index, kExprCallRef,
kExprLocalGet, 0, kExprI32Add])
.exportAs("main");
let instance = builder.instantiate();
assertEquals(14, instance.exports.main(10));
})();
(function CallRefSpecFailedTest() {
let builder = new WasmModuleBuilder();
// h(x) = x - 1
builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
// f(x) = x - 2
let callee = builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);
let global = builder.addGlobal(wasmRefType(1), false,
WasmInitExpr.RefFunc(callee.index));
// g(x) = f(5) + x
builder.addFunction("main", kSig_i_i)
.addBody([kExprI32Const, 5, kExprGlobalGet, global.index, kExprCallRef,
kExprLocalGet, 0, kExprI32Add])
.exportAs("main");
let instance = builder.instantiate();
assertEquals(13, instance.exports.main(10));
})();
(function CallReturnRefSpecSucceededTest() {
let builder = new WasmModuleBuilder();
// f(x) = x - 1
let callee = builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
let global = builder.addGlobal(wasmRefType(0), false,
WasmInitExpr.RefFunc(callee.index));
// g(x) = f(5 + x)
builder.addFunction("main", kSig_i_i)
.addBody([kExprI32Const, 5, kExprLocalGet, 0, kExprI32Add,
kExprGlobalGet, global.index, kExprReturnCallRef])
.exportAs("main");
let instance = builder.instantiate();
assertEquals(14, instance.exports.main(10));
})();
(function CallReturnRefSpecFailedTest() {
let builder = new WasmModuleBuilder();
// h(x) = x - 1
builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);
// f(x) = x - 2
let callee = builder.addFunction("callee", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);
let global = builder.addGlobal(wasmRefType(1), false,
WasmInitExpr.RefFunc(callee.index));
// g(x) = f(5 + x)
builder.addFunction("main", kSig_i_i)
.addBody([kExprI32Const, 5, kExprLocalGet, 0, kExprI32Add,
kExprGlobalGet, global.index, kExprReturnCallRef])
.exportAs("main");
let instance = builder.instantiate();
assertEquals(13, instance.exports.main(10));
})();
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