Commit bd72152e authored by Paolo Severini's avatar Paolo Severini Committed by V8 LUCI CQ

[fastcall] Add Wasm entry for Fast API calls

Allow Wasm to generate calls directly to Fast API C functions.
This massively reduces the overhead of these calls (~300%).
Currently options parameter is not supported.

This is a rebase of the work originally done by devsnek in:
https://chromium-review.googlesource.com/c/v8/v8/+/2718666.

Bug: chromium:1052746
Change-Id: I1bb1de68b440044cc8a4e528adf9d8e0e6692a07
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3364356Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarManos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#78664}
parent 0a61fa51
......@@ -7,6 +7,7 @@
#include <iosfwd>
#include "include/v8-fast-api-calls.h"
#include "src/base/bits.h"
#include "src/common/globals.h"
#include "src/flags/flags.h"
......@@ -274,6 +275,35 @@ class MachineType {
}
}
static MachineType TypeForCType(const CTypeInfo& type) {
switch (type.GetType()) {
case CTypeInfo::Type::kVoid:
return MachineType::AnyTagged();
case CTypeInfo::Type::kBool:
return MachineType::Bool();
case CTypeInfo::Type::kInt32:
return MachineType::Int32();
case CTypeInfo::Type::kUint32:
return MachineType::Uint32();
case CTypeInfo::Type::kInt64:
return MachineType::Int64();
case CTypeInfo::Type::kAny:
static_assert(
sizeof(AnyCType) == kInt64Size,
"CTypeInfo::Type::kAny is assumed to be of size 64 bits.");
return MachineType::Int64();
case CTypeInfo::Type::kUint64:
return MachineType::Uint64();
case CTypeInfo::Type::kFloat32:
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kApiObject:
return MachineType::AnyTagged();
}
}
constexpr bool LessThanOrEqualPointerSize() const {
return ElementSizeLog2Of(this->representation()) <= kSystemPointerSizeLog2;
}
......
......@@ -4979,36 +4979,6 @@ void EffectControlLinearizer::LowerStoreMessage(Node* node) {
__ StoreField(AccessBuilder::ForExternalIntPtr(), offset, object_pattern);
}
namespace {
MachineType MachineTypeFor(CTypeInfo::Type type) {
switch (type) {
case CTypeInfo::Type::kVoid:
return MachineType::AnyTagged();
case CTypeInfo::Type::kBool:
return MachineType::Bool();
case CTypeInfo::Type::kInt32:
return MachineType::Int32();
case CTypeInfo::Type::kUint32:
return MachineType::Uint32();
case CTypeInfo::Type::kInt64:
return MachineType::Int64();
case CTypeInfo::Type::kAny:
static_assert(sizeof(AnyCType) == 8,
"CTypeInfo::Type::kAny is assumed to be of size 64 bits.");
return MachineType::Int64();
case CTypeInfo::Type::kUint64:
return MachineType::Uint64();
case CTypeInfo::Type::kFloat32:
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kApiObject:
return MachineType::AnyTagged();
}
}
} // namespace
Node* EffectControlLinearizer::AdaptFastCallTypedArrayArgument(
Node* node, ElementsKind expected_elements_kind,
GraphAssemblerLabel<0>* bailout) {
......@@ -5350,13 +5320,14 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
MachineSignature::Builder builder(
graph()->zone(), 1, c_arg_count + (c_signature->HasOptions() ? 1 : 0));
MachineType return_type = MachineTypeFor(c_signature->ReturnInfo().GetType());
MachineType return_type =
MachineType::TypeForCType(c_signature->ReturnInfo());
builder.AddReturn(return_type);
for (int i = 0; i < c_arg_count; ++i) {
CTypeInfo type = c_signature->ArgumentInfo(i);
MachineType machine_type =
type.GetSequenceType() == CTypeInfo::SequenceType::kScalar
? MachineTypeFor(type.GetType())
? MachineType::TypeForCType(type)
: MachineType::AnyTagged();
builder.AddParam(machine_type);
}
......
......@@ -4,6 +4,8 @@
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/globals.h"
namespace v8 {
namespace internal {
namespace compiler {
......@@ -78,6 +80,44 @@ OverloadsResolutionResult ResolveOverloads(
return OverloadsResolutionResult::Invalid();
}
bool CanOptimizeFastSignature(const CFunctionInfo* c_signature) {
USE(c_signature);
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
if (c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kFloat32 ||
c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kFloat64) {
return false;
}
#endif
#ifndef V8_TARGET_ARCH_64_BIT
if (c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kInt64 ||
c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kUint64) {
return false;
}
#endif
for (unsigned int i = 0; i < c_signature->ArgumentCount(); ++i) {
USE(i);
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
if (c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kFloat32 ||
c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kFloat64) {
return false;
}
#endif
#ifndef V8_TARGET_ARCH_64_BIT
if (c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kInt64 ||
c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kUint64) {
return false;
}
#endif
}
return true;
}
} // namespace fast_api_call
} // namespace compiler
} // namespace internal
......
......@@ -42,6 +42,8 @@ OverloadsResolutionResult ResolveOverloads(
Zone* zone, const FastApiCallFunctionVector& candidates,
unsigned int arg_count);
bool CanOptimizeFastSignature(const CFunctionInfo* c_signature);
} // namespace fast_api_call
} // namespace compiler
} // namespace internal
......
......@@ -6,7 +6,6 @@
#include <functional>
#include "include/v8-fast-api-calls.h"
#include "src/api/api-inl.h"
#include "src/base/small-vector.h"
#include "src/builtins/builtins-promise.h"
......@@ -19,6 +18,7 @@
#include "src/compiler/allocation-builder.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
......@@ -3518,42 +3518,6 @@ Reduction JSCallReducer::ReduceCallWasmFunction(
}
#endif // V8_ENABLE_WEBASSEMBLY
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
namespace {
bool HasFPParamsInSignature(const CFunctionInfo* c_signature) {
if (c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kFloat32 ||
c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kFloat64) {
return true;
}
for (unsigned int i = 0; i < c_signature->ArgumentCount(); ++i) {
if (c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kFloat32 ||
c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kFloat64) {
return true;
}
}
return false;
}
} // namespace
#endif
#ifndef V8_TARGET_ARCH_64_BIT
namespace {
bool Has64BitIntegerParamsInSignature(const CFunctionInfo* c_signature) {
if (c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kInt64 ||
c_signature->ReturnInfo().GetType() == CTypeInfo::Type::kUint64) {
return true;
}
for (unsigned int i = 0; i < c_signature->ArgumentCount(); ++i) {
if (c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kInt64 ||
c_signature->ArgumentInfo(i).GetType() == CTypeInfo::Type::kUint64) {
return true;
}
}
return false;
}
} // namespace
#endif
// Given a FunctionTemplateInfo, checks whether the fast API call can be
// optimized, applying the initial step of the overload resolution algorithm:
// Given an overload set function_template_info.c_signatures, and a list of
......@@ -3598,16 +3562,9 @@ FastApiCallFunctionVector CanOptimizeFastCall(
const size_t len = c_signature->ArgumentCount() - kReceiver;
bool optimize_to_fast_call = (len == arg_count);
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
optimize_to_fast_call =
optimize_to_fast_call && !HasFPParamsInSignature(c_signature);
#else
USE(c_signature);
#endif
#ifndef V8_TARGET_ARCH_64_BIT
optimize_to_fast_call =
optimize_to_fast_call && !Has64BitIntegerParamsInSignature(c_signature);
#endif
optimize_to_fast_call &&
fast_api_call::CanOptimizeFastSignature(c_signature);
if (optimize_to_fast_call) {
result.push_back({functions[i], c_signature});
......
This diff is collapsed.
......@@ -15,6 +15,7 @@
// Clients of this interface shouldn't depend on lots of compiler internals.
// Do not include anything from src/compiler here!
#include "src/base/small-vector.h"
#include "src/objects/js-function.h"
#include "src/runtime/runtime.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/function-compiler.h"
......@@ -72,6 +73,7 @@ enum class WasmImportCallKind : uint8_t {
kLinkError, // static Wasm->Wasm type error
kRuntimeTypeError, // runtime Wasm->JS type error
kWasmToCapi, // fast Wasm->C-API call
kWasmToJSFastApi, // fast Wasm->JS Fast API C call
kWasmToWasm, // fast Wasm->Wasm call
kJSFunctionArityMatch, // fast Wasm->JS call
kJSFunctionArityMismatch, // Wasm->JS, needs adapter frame
......@@ -131,6 +133,11 @@ V8_EXPORT_PRIVATE wasm::WasmCompilationResult CompileWasmImportCallWrapper(
wasm::WasmCode* CompileWasmCapiCallWrapper(wasm::NativeModule*,
const wasm::FunctionSig*);
// Compiles a wrapper to call a Fast API function from Wasm.
wasm::WasmCode* CompileWasmJSFastCallWrapper(wasm::NativeModule*,
const wasm::FunctionSig*,
Handle<JSFunction> target);
// Returns an OptimizedCompilationJob object for a JS to Wasm wrapper.
std::unique_ptr<OptimizedCompilationJob> NewJSToWasmCompilationJob(
Isolate* isolate, const wasm::FunctionSig* sig,
......
......@@ -39,6 +39,8 @@ namespace {
class FastCApiObject {
public:
static FastCApiObject& instance();
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
static AnyCType AddAllFastCallbackPatch(AnyCType receiver,
AnyCType should_fallback,
......@@ -75,6 +77,37 @@ class FastCApiObject {
static_cast<double>(arg_i64) + static_cast<double>(arg_u64) +
static_cast<double>(arg_f32) + arg_f64;
}
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
static AnyCType AddAllFastCallbackNoOptionsPatch(
AnyCType receiver, AnyCType should_fallback, AnyCType arg_i32,
AnyCType arg_u32, AnyCType arg_i64, AnyCType arg_u64, AnyCType arg_f32,
AnyCType arg_f64) {
AnyCType ret;
ret.double_value = AddAllFastCallbackNoOptions(
receiver.object_value, should_fallback.bool_value, arg_i32.int32_value,
arg_u32.uint32_value, arg_i64.int64_value, arg_u64.uint64_value,
arg_f32.float_value, arg_f64.double_value);
return ret;
}
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
static double AddAllFastCallbackNoOptions(Local<Object> receiver,
bool should_fallback,
int32_t arg_i32, uint32_t arg_u32,
int64_t arg_i64, uint64_t arg_u64,
float arg_f32, double arg_f64) {
// For Wasm call, we don't pass FastCApiObject as the receiver, so we need
// to retrieve the FastCApiObject instance from a static variable.
DCHECK(Utils::OpenHandle(*receiver)->IsJSGlobalProxy() ||
Utils::OpenHandle(*receiver)->IsUndefined());
static FastCApiObject& self = FastCApiObject::instance();
self.fast_call_count_++;
return static_cast<double>(arg_i32) + static_cast<double>(arg_u32) +
static_cast<double>(arg_i64) + static_cast<double>(arg_u64) +
static_cast<double>(arg_f32) + arg_f64;
}
static void AddAllSlowCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
......@@ -620,6 +653,9 @@ class FastCApiObject {
thread_local FastCApiObject kFastCApiObject;
} // namespace
// static
FastCApiObject& FastCApiObject::instance() { return kFastCApiObject; }
void CreateFastCAPIObject(const FunctionCallbackInfo<Value>& info) {
if (!info.IsConstructCall()) {
info.GetIsolate()->ThrowError(
......@@ -765,6 +801,7 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
FastCApiObject::AddAll32BitIntFastCallback_5ArgsPatch));
const CFunction c_function_overloads[] = {add_all_32bit_int_6args_c_func,
add_all_32bit_int_5args_c_func};
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "overloaded_add_all_32bit_int",
FunctionTemplate::NewWithCFunctionOverloads(
......@@ -772,6 +809,16 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, {c_function_overloads, 2}));
CFunction add_all_no_options_c_func = CFunction::Make(
FastCApiObject::AddAllFastCallbackNoOptions V8_IF_USE_SIMULATOR(
FastCApiObject::AddAllFastCallbackNoOptionsPatch));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_all_no_options",
FunctionTemplate::New(
isolate, FastCApiObject::AddAllSlowCallback, Local<Value>(),
Local<Signature>(), 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_all_no_options_c_func));
CFunction add_32bit_int_c_func = CFunction::Make(
FastCApiObject::Add32BitIntFastCallback V8_IF_USE_SIMULATOR(
FastCApiObject::Add32BitIntFastCallbackPatch));
......@@ -781,6 +828,7 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
isolate, FastCApiObject::Add32BitIntSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_32bit_int_c_func));
CFunction is_valid_api_object_c_func =
CFunction::Make(FastCApiObject::IsFastCApiObjectFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
......@@ -789,6 +837,7 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
isolate, FastCApiObject::IsFastCApiObjectSlowCallback,
Local<Value>(), signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &is_valid_api_object_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "fast_call_count",
FunctionTemplate::New(
......
......@@ -1197,6 +1197,19 @@ bool InstanceBuilder::ProcessImportedFunction(
isolate_->factory()->undefined_value());
break;
}
case compiler::WasmImportCallKind::kWasmToJSFastApi: {
NativeModule* native_module = instance->module_object().native_module();
DCHECK(js_receiver->IsJSFunction());
Handle<JSFunction> function = Handle<JSFunction>::cast(js_receiver);
WasmCodeRefScope code_ref_scope;
WasmCode* wasm_code = compiler::CompileWasmJSFastCallWrapper(
native_module, expected_sig, function);
ImportedFunctionEntry entry(instance, func_index);
entry.SetWasmToJs(isolate_, js_receiver, wasm_code,
isolate_->factory()->undefined_value());
break;
}
default: {
// The imported function is a callable.
......@@ -1611,7 +1624,8 @@ void InstanceBuilder::CompileImportWrappers(
compiler::WasmImportCallKind kind = resolved.kind;
if (kind == compiler::WasmImportCallKind::kWasmToWasm ||
kind == compiler::WasmImportCallKind::kLinkError ||
kind == compiler::WasmImportCallKind::kWasmToCapi) {
kind == compiler::WasmImportCallKind::kWasmToCapi ||
kind == compiler::WasmImportCallKind::kWasmToJSFastApi) {
continue;
}
......
// Copyright 2022 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: --turbo-fast-api-calls --expose-fast-api
load('test/mjsunit/wasm/wasm-module-builder.js');
assertThrows(() => d8.test.FastCAPI());
const fast_c_api = new d8.test.FastCAPI();
function buildWasm(name, sig, body) {
const builder = new WasmModuleBuilder();
const add_all_no_options = builder.addImport(
'fast_c_api',
'add_all_no_options',
makeSig(
[kWasmI32, kWasmI32, kWasmI32, kWasmI64, kWasmI64, kWasmF32, kWasmF64],
[kWasmF64],
),
);
const add_all_no_options_mismatch = builder.addImport(
'fast_c_api',
'add_all_no_options',
makeSig(
[kWasmI32, kWasmI32, kWasmI32, kWasmI64, kWasmF32, kWasmI64, kWasmF64],
[kWasmF64],
),
);
const add_all_nested_bound = builder.addImport(
'fast_c_api',
'add_all_nested_bound',
makeSig(
[kWasmI32, kWasmI32, kWasmI32, kWasmI64, kWasmI64, kWasmF32, kWasmF64],
[kWasmF64],
),
);
builder
.addFunction(name, sig)
.addBody(body({
add_all_no_options,
add_all_no_options_mismatch,
add_all_nested_bound,
}))
.exportFunc();
const x = {};
const module = builder.instantiate({
fast_c_api: {
add_all_no_options: fast_c_api.add_all_no_options.bind(fast_c_api),
add_all_no_options_mismatch: fast_c_api.add_all_no_options.bind(fast_c_api),
add_all_nested_bound: fast_c_api.add_all_no_options
.bind(fast_c_api)
.bind(x),
},
});
return module.exports[name];
}
// ----------- add_all -----------
// `add_all` has the following signature:
// double add_all(bool /*should_fallback*/, int32_t, uint32_t,
// int64_t, uint64_t, float, double)
const max_safe_float = 2**24 - 1;
const add_all_result = -42 + 45 + Number.MIN_SAFE_INTEGER + Number.MAX_SAFE_INTEGER +
max_safe_float * 0.5 + Math.PI;
const add_all_wasm = buildWasm(
'add_all_wasm', makeSig([], [kWasmF64]),
({ add_all_no_options }) => [
...wasmI32Const(0),
...wasmI32Const(-42),
...wasmI32Const(45),
kExprI64Const, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x70, // Number.MIN_SAFE_INTEGER
kExprI64Const, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // Number.MAX_SAFE_INTEGER
...wasmF32Const(max_safe_float * 0.5),
...wasmF64Const(Math.PI),
kExprCallFunction, add_all_no_options,
kExprReturn,
],
);
if (fast_c_api.supports_fp_params) {
// Test wasm hits fast path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all_wasm());
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
} else {
// Test wasm hits slow path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all_wasm());
assertEquals(0, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
}
// ----------- Test add_all signature mismatch -----------
const add_all_mismatch_wasm = buildWasm(
'add_all_mismatch_wasm', makeSig([], [kWasmF64]),
({ add_all_no_options_mismatch }) => [
...wasmI32Const(0),
...wasmI32Const(45),
...wasmI32Const(-42),
kExprI64Const, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // Number.MAX_SAFE_INTEGER
...wasmF32Const(max_safe_float * 0.5),
kExprI64Const, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x70, // Number.MIN_SAFE_INTEGER
...wasmF64Const(Math.PI),
kExprCallFunction, add_all_no_options_mismatch,
kExprReturn,
],
);
// Test that wasm takes slow path.
fast_c_api.reset_counts();
add_all_mismatch_wasm();
assertEquals(0, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
// ----------- Test add_all nested bound function -----------
const add_all_nested_bound_wasm = buildWasm(
'add_all_nested_bound_wasm', makeSig([], [kWasmF64]),
({ add_all_nested_bound }) => [
...wasmI32Const(0),
...wasmI32Const(-42),
...wasmI32Const(45),
kExprI64Const, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x70, // Number.MIN_SAFE_INTEGER
kExprI64Const, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, // Number.MAX_SAFE_INTEGER
...wasmF32Const(max_safe_float * 0.5),
...wasmF64Const(Math.PI),
kExprCallFunction, add_all_nested_bound,
kExprReturn,
],
);
// Test wasm hits slow path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all_nested_bound_wasm());
assertEquals(0, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
......@@ -472,6 +472,8 @@
# Tests tracing when generating wasm in TurboFan.
'tools/compiler-trace-flags-wasm': [SKIP],
'compiler/fast-api-calls-wasm': [SKIP],
}], # not has_webassembly or variant == jitless
##############################################################################
......
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