[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/+/3364356
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#78664}
This commit is contained in:
Paolo Severini 2022-01-14 17:33:56 +01:00 committed by V8 LUCI CQ
parent 0a61fa5184
commit bd72152e7d
11 changed files with 573 additions and 79 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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});

View File

@ -6,6 +6,7 @@
#include <memory>
#include "src/api/api-inl.h"
#include "src/base/optional.h"
#include "src/base/platform/elapsed-timer.h"
#include "src/base/platform/platform.h"
@ -25,6 +26,7 @@
#include "src/compiler/common-operator.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/diamond.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/graph.h"
@ -37,6 +39,7 @@
#include "src/compiler/pipeline.h"
#include "src/compiler/zone-stats.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/simulator.h"
#include "src/heap/factory.h"
#include "src/logging/counters.h"
#include "src/logging/log.h"
@ -7262,6 +7265,110 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
if (ContainsInt64(sig_)) LowerInt64(kCalledFromWasm);
}
void BuildJSFastApiCallWrapper(Handle<JSFunction> target) {
// Here 'callable_node' must be equal to 'target' but we cannot pass a
// HeapConstant(target) because WasmCode::Validate() fails with
// Unexpected mode: FULL_EMBEDDED_OBJECT.
Node* callable_node = gasm_->Load(
MachineType::TaggedPointer(), Param(0),
wasm::ObjectAccess::ToTagged(WasmApiFunctionRef::kCallableOffset));
Node* native_context = gasm_->Load(
MachineType::TaggedPointer(), Param(0),
wasm::ObjectAccess::ToTagged(WasmApiFunctionRef::kNativeContextOffset));
Node* undefined_node = UndefinedValue();
Node* receiver_node =
BuildReceiverNode(callable_node, native_context, undefined_node);
SharedFunctionInfo shared = target->shared();
FunctionTemplateInfo api_func_data = shared.get_api_func_data();
const Address c_address = api_func_data.GetCFunction(0);
const v8::CFunctionInfo* c_signature = api_func_data.GetCSignature(0);
int c_arg_count = c_signature->ArgumentCount();
BuildModifyThreadInWasmFlag(false);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Address c_functions[] = {c_address};
const v8::CFunctionInfo* const c_signatures[] = {c_signature};
target->GetIsolate()->simulator_data()->RegisterFunctionsAndSignatures(
c_functions, c_signatures, 1);
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
MachineSignature::Builder builder(graph()->zone(), 1, c_arg_count);
builder.AddReturn(MachineType::TypeForCType(c_signature->ReturnInfo()));
for (int i = 0; i < c_arg_count; i += 1) {
builder.AddParam(MachineType::TypeForCType(c_signature->ArgumentInfo(i)));
}
base::SmallVector<Node*, 16> args(c_arg_count + 3);
int pos = 0;
args[pos++] = mcgraph()->ExternalConstant(
ExternalReference::Create(c_address, ExternalReference::FAST_C_CALL));
auto store_stack = [this](Node* node) -> Node* {
constexpr int kAlign = alignof(uintptr_t);
constexpr int kSize = sizeof(uintptr_t);
Node* stack_slot = gasm_->StackSlot(kSize, kAlign);
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
stack_slot, 0, node);
return stack_slot;
};
// Set receiver.
args[pos++] = store_stack(receiver_node);
for (int i = 1; i < c_arg_count; i += 1) {
switch (c_signature->ArgumentInfo(i).GetType()) {
case CTypeInfo::Type::kV8Value:
args[pos++] = store_stack(Param(i));
break;
default:
args[pos++] = Param(i);
break;
}
}
DCHECK(!c_signature->HasOptions());
args[pos++] = effect();
args[pos++] = control();
auto call_descriptor =
Linkage::GetSimplifiedCDescriptor(graph()->zone(), builder.Build());
// CPU profiler support.
Node* target_address = gasm_->ExternalConstant(
ExternalReference::fast_api_call_target_address(target->GetIsolate()));
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
target_address, 0, gasm_->IntPtrConstant(c_address));
// Disable JS execution.
Node* javascript_execution_assert = gasm_->ExternalConstant(
ExternalReference::javascript_execution_assert(target->GetIsolate()));
gasm_->Store(
StoreRepresentation(MachineRepresentation::kWord8, kNoWriteBarrier),
javascript_execution_assert, 0, gasm_->Int32Constant(0));
// Execute the fast call API.
Node* call = gasm_->Call(call_descriptor, pos, args.begin());
// Reenable JS execution.
gasm_->Store(
StoreRepresentation(MachineRepresentation::kWord8, kNoWriteBarrier),
javascript_execution_assert, 0, gasm_->Int32Constant(1));
// Reset the CPU profiler target address.
gasm_->Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
target_address, 0, gasm_->IntPtrConstant(0));
BuildModifyThreadInWasmFlag(true);
Return(call);
}
void BuildJSToJSWrapper() {
int wasm_count = static_cast<int>(sig_->parameter_count());
@ -7492,6 +7599,98 @@ std::unique_ptr<OptimizedCompilationJob> NewJSToWasmCompilationJob(
std::move(debug_name), WasmAssemblerOptions());
}
static MachineRepresentation NormalizeFastApiRepresentation(
const CTypeInfo& info) {
MachineType t = MachineType::TypeForCType(info);
// Wasm representation of bool is i32 instead of i1.
if (t.semantic() == MachineSemantic::kBool) {
return MachineRepresentation::kWord32;
}
return t.representation();
}
static bool IsSupportedWasmFastApiFunction(
const wasm::FunctionSig* expected_sig, Handle<SharedFunctionInfo> shared) {
if (!shared->IsApiFunction()) {
return false;
}
if (shared->get_api_func_data().GetCFunctionsCount() == 0) {
return false;
}
if (!shared->get_api_func_data().accept_any_receiver()) {
return false;
}
if (!shared->get_api_func_data().signature().IsUndefined()) {
// TODO(wasm): CFunctionInfo* signature check.
return false;
}
const CFunctionInfo* info = shared->get_api_func_data().GetCSignature(0);
if (!fast_api_call::CanOptimizeFastSignature(info)) {
return false;
}
// Options are not supported yet.
if (info->HasOptions()) {
return false;
}
const auto log_imported_function_mismatch = [&shared]() {
if (FLAG_trace_opt) {
CodeTracer::Scope scope(shared->GetIsolate()->GetCodeTracer());
PrintF(scope.file(), "[disabled optimization for ");
shared->ShortPrint(scope.file());
PrintF(scope.file(),
", reason: the signature of the imported function in the Wasm "
"module doesn't match that of the Fast API function]\n");
}
};
// C functions only have one return value.
if (expected_sig->return_count() > 1) {
// Here and below, we log when the function we call is declared as an Api
// function but we cannot optimize the call, which might be unxepected. In
// that case we use the "slow" path making a normal Wasm->JS call and
// calling the "slow" callback specified in FunctionTemplate::New().
log_imported_function_mismatch();
return false;
}
CTypeInfo return_info = info->ReturnInfo();
// Unsupported if return type doesn't match.
if (expected_sig->return_count() == 0 &&
return_info.GetType() != CTypeInfo::Type::kVoid) {
log_imported_function_mismatch();
return false;
}
// Unsupported if return type doesn't match.
if (expected_sig->return_count() == 1) {
if (return_info.GetType() == CTypeInfo::Type::kVoid) {
log_imported_function_mismatch();
return false;
}
if (NormalizeFastApiRepresentation(return_info) !=
expected_sig->GetReturn(0).machine_type().representation()) {
log_imported_function_mismatch();
return false;
}
}
// Unsupported if arity doesn't match.
if (expected_sig->parameter_count() != info->ArgumentCount() - 1) {
log_imported_function_mismatch();
return false;
}
// Unsupported if any argument types don't match.
for (unsigned int i = 0; i < expected_sig->parameter_count(); i += 1) {
// Arg 0 is the receiver, skip over it since wasm doesn't
// have a concept of receivers.
CTypeInfo arg = info->ArgumentInfo(i + 1);
if (NormalizeFastApiRepresentation(arg) !=
expected_sig->GetParam(i).machine_type().representation()) {
log_imported_function_mismatch();
return false;
}
}
return true;
}
WasmImportData ResolveWasmImportCall(
Handle<JSReceiver> callable, const wasm::FunctionSig* expected_sig,
const wasm::WasmModule* module,
@ -7535,6 +7734,35 @@ WasmImportData ResolveWasmImportCall(
if (!wasm::IsJSCompatibleSignature(expected_sig, module, enabled_features)) {
return {WasmImportCallKind::kRuntimeTypeError, callable, no_suspender};
}
// Check if this can be a JS fast API call.
if (FLAG_turbo_fast_api_calls &&
(callable->IsJSFunction() || callable->IsJSBoundFunction())) {
Handle<JSFunction> target;
if (callable->IsJSBoundFunction()) {
Handle<JSBoundFunction> bound_target =
Handle<JSBoundFunction>::cast(callable);
// Nested bound functions and arguments not supported yet.
if (bound_target->bound_arguments().length() == 0 &&
!bound_target->bound_target_function().IsJSBoundFunction()) {
Handle<JSReceiver> bound_target_function = handle(
bound_target->bound_target_function(), callable->GetIsolate());
if (bound_target_function->IsJSFunction()) {
target = Handle<JSFunction>::cast(bound_target_function);
}
}
} else {
DCHECK(callable->IsJSFunction());
target = Handle<JSFunction>::cast(callable);
}
if (!target.is_null()) {
Handle<SharedFunctionInfo> shared(target->shared(), target->GetIsolate());
if (IsSupportedWasmFastApiFunction(expected_sig, shared)) {
return {WasmImportCallKind::kWasmToJSFastApi, target, no_suspender};
}
}
}
// For JavaScript calls, determine whether the target has an arity match.
if (callable->IsJSFunction()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(callable);
@ -7723,6 +7951,7 @@ wasm::WasmCompilationResult CompileWasmImportCallWrapper(
wasm::Suspend suspend) {
DCHECK_NE(WasmImportCallKind::kLinkError, kind);
DCHECK_NE(WasmImportCallKind::kWasmToWasm, kind);
DCHECK_NE(WasmImportCallKind::kWasmToJSFastApi, kind);
// Check for math intrinsics first.
if (FLAG_wasm_math_intrinsics &&
@ -7821,6 +8050,58 @@ wasm::WasmCode* CompileWasmCapiCallWrapper(wasm::NativeModule* native_module,
return published_code;
}
wasm::WasmCode* CompileWasmJSFastCallWrapper(wasm::NativeModule* native_module,
const wasm::FunctionSig* sig,
Handle<JSFunction> target) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.CompileWasmJSFastCallWrapper");
Zone zone(wasm::GetWasmEngine()->allocator(), ZONE_NAME, kCompressGraphZone);
// TODO(jkummerow): Extract common code into helper method.
SourcePositionTable* source_positions = nullptr;
MachineGraph* mcgraph = zone.New<MachineGraph>(
zone.New<Graph>(&zone), zone.New<CommonOperatorBuilder>(&zone),
zone.New<MachineOperatorBuilder>(
&zone, MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags(),
InstructionSelector::AlignmentRequirements()));
WasmWrapperGraphBuilder builder(
&zone, mcgraph, sig, native_module->module(),
WasmGraphBuilder::kWasmApiFunctionRefMode, nullptr, source_positions,
StubCallMode::kCallWasmRuntimeStub, native_module->enabled_features());
// Set up the graph start.
int param_count = static_cast<int>(sig->parameter_count()) +
1 /* offset for first parameter index being -1 */ +
1 /* Wasm instance */ + 1 /* kExtraCallableParam */;
builder.Start(param_count);
builder.BuildJSFastApiCallWrapper(target);
// Run the compiler pipeline to generate machine code.
CallDescriptor* call_descriptor =
GetWasmCallDescriptor(&zone, sig, WasmCallKind::kWasmImportWrapper);
if (mcgraph->machine()->Is32()) {
call_descriptor = GetI32WasmCallDescriptor(&zone, call_descriptor);
}
const char* debug_name = "WasmJSFastApiCall";
wasm::WasmCompilationResult result = Pipeline::GenerateCodeForWasmNativeStub(
call_descriptor, mcgraph, CodeKind::WASM_TO_JS_FUNCTION, debug_name,
WasmStubAssemblerOptions(), source_positions);
{
wasm::CodeSpaceWriteScope code_space_write_scope(native_module);
std::unique_ptr<wasm::WasmCode> wasm_code = native_module->AddCode(
wasm::kAnonymousFuncIndex, result.code_desc, result.frame_slot_count,
result.tagged_parameter_slots,
result.protected_instructions_data.as_vector(),
result.source_positions.as_vector(), wasm::WasmCode::kWasmToJsWrapper,
wasm::ExecutionTier::kNone, wasm::kNoDebugging);
return native_module->PublishCode(std::move(wasm_code));
}
}
MaybeHandle<Code> CompileWasmToJSWrapper(Isolate* isolate,
const wasm::FunctionSig* sig,
WasmImportCallKind kind,

View File

@ -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,

View File

@ -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(

View File

@ -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;
}

View File

@ -0,0 +1,141 @@
// 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());

View File

@ -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
##############################################################################