[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:
parent
0a61fa5184
commit
bd72152e7d
@ -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});
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
141
test/mjsunit/compiler/fast-api-calls-wasm.js
Normal file
141
test/mjsunit/compiler/fast-api-calls-wasm.js
Normal 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());
|
@ -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
|
||||
|
||||
##############################################################################
|
||||
|
Loading…
Reference in New Issue
Block a user