Files
sdk/runtime/vm/native_arguments.h
T
Daco Harkes 47357d8535 Reland "[vm/ffi] Optimize @Native calls"
Original change in patchset 1.

The reland contains a fix for handles. The objects passed in handles
must live on the stack (or in the heap) so that that memory location
can be used as a handle.

This CL introduces a new allocation policy `RequiresRegister` which
forces constants to be spilled to the stack during register
allocation.
Note: Parameters to FfiCall instructions are currently not used in a
way that can make the parameters assigned to registers. So only
constants are treated currently. If we have a way to reproduce params
being assigned to registers, then we can force spilling for
non-consts as well in the register allocator.

Original change's description:
> [vm/ffi] Optimize `@Native` calls
>
> This CL removes static fields for storing the `@Native`'s function
> addresses. Instead, the function addresses are stored in the object
> pool for all archs except for ia32. ia32 has no AOT and no AppJit
> snapshots, so the addresses are directly embedded in the code.
>
> This CL removes the closure wrapping for `@Native`s. Instead of
> `pointer.asFunctionInternal()()` where `asFunction` returns a closure
> which contains the trampoline, the function is compiled to a body
> which contains the trampoline `Native()`. This is possible for
> `@Native`s because the dylib and symbol names are known statically.
>
> Doing the compilation in kernel_to_il instead of a CFE transform
> enables supporting static linking later. (The alternative would have
> been to transform in the cfe to a `@pragma('vm:cachable-idempotent')`
> instead of constructing the IL in kernel_to_il.
>
> To enable running resolution in ia32 in kernel_to_il.cc, the
> resolution function has been made available via
> `runtime/lib/ffi_dynamic_library.h`.
>
> Because the new calls are simply static calls, the TFA can figure
> out const arguments flowing to these calls. This leads to constant
> locations in the parameters to FfiCalls. So, this CL also introduces
> logic to move constants into `NativeLocation`s.

TEST=tests/ffi/vmspecific_ffi_native_handles_test.dart
TEST=tests/ffi/function_*_native_(leaf_)test.dart
TEST=pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart

Closes: https://github.com/dart-lang/sdk/issues/47625
CoreLibraryReviewExempt: Internal FFI implementation changes
Change-Id: Ia1401b335524dcbf50c663a8770d9a02802923df
Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-release-x64-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333841
Reviewed-by: Slava Egorov <vegorov@google.com>
2023-11-22 16:13:42 +00:00

278 lines
9.7 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_VM_NATIVE_ARGUMENTS_H_
#define RUNTIME_VM_NATIVE_ARGUMENTS_H_
#include "platform/assert.h"
#include "platform/memory_sanitizer.h"
#include "vm/globals.h"
#include "vm/simulator.h"
#include "vm/stub_code.h"
namespace dart {
// Forward declarations.
class BootstrapNatives;
class Object;
class Simulator;
class Thread;
#if defined(TESTING) || defined(DEBUG)
#if defined(USING_SIMULATOR)
#define CHECK_STACK_ALIGNMENT \
{ \
uword current_sp = Simulator::Current()->get_register(SPREG); \
ASSERT(Utils::IsAligned(current_sp, OS::ActivationFrameAlignment())); \
}
#elif defined(DART_HOST_OS_WINDOWS)
// The compiler may dynamically align the stack on Windows, so do not check.
#define CHECK_STACK_ALIGNMENT \
{}
#else
#define CHECK_STACK_ALIGNMENT \
{ \
uword (*func)() = reinterpret_cast<uword (*)()>( \
StubCode::GetCStackPointer().EntryPoint()); \
uword current_sp = func(); \
ASSERT(Utils::IsAligned(current_sp, OS::ActivationFrameAlignment())); \
}
#endif
#define DEOPTIMIZE_ALOT \
if (FLAG_deoptimize_alot) { \
DeoptimizeFunctionsOnStack(); \
}
#else
#define CHECK_STACK_ALIGNMENT \
{}
#define DEOPTIMIZE_ALOT \
{}
#endif
// Class NativeArguments is used to access arguments passed in from
// generated dart code to a runtime function or a dart library native
// function. It is also used to set the return value if any at the slot
// reserved for return values.
// All runtime function/dart library native functions have the
// following signature:
// void function_name(NativeArguments arguments);
// Inside the function, arguments are accessed as follows:
// const Instance& arg0 = Instance::CheckedHandle(arguments.NativeArgAt(0));
// const Smi& arg1 = Smi::CheckedHandle(arguments.NativeArgAt(1));
// If the function is generic, type arguments are accessed as follows:
// const TypeArguments& type_args =
// TypeArguments::Handle(arguments.NativeTypeArgs());
// The return value is set as follows:
// arguments.SetReturn(result);
// NOTE: Since we pass 'this' as a pass-by-value argument in the stubs we don't
// have DISALLOW_COPY_AND_ASSIGN in the class definition and do not make it a
// subclass of ValueObject.
class NativeArguments {
public:
Thread* thread() const { return thread_; }
// Includes type arguments vector.
int ArgCount() const { return ArgcBits::decode(argc_tag_); }
ObjectPtr ArgAt(int index) const {
ASSERT((index >= 0) && (index < ArgCount()));
ObjectPtr* arg_ptr = &(argv_[-index]);
// Tell MemorySanitizer the ObjectPtr was initialized (by generated code).
MSAN_UNPOISON(arg_ptr, kWordSize);
return *arg_ptr;
}
void SetArgAt(int index, const Object& value) const {
ASSERT(thread_->execution_state() == Thread::kThreadInVM);
ASSERT((index >= 0) && (index < ArgCount()));
argv_[-index] = value.ptr();
}
// Does not include hidden type arguments vector.
int NativeArgCount() const {
int function_bits = FunctionBits::decode(argc_tag_);
return ArgCount() - NumHiddenArgs(function_bits);
}
ObjectPtr NativeArg0() const {
int function_bits = FunctionBits::decode(argc_tag_);
if ((function_bits & (kClosureFunctionBit | kInstanceFunctionBit)) ==
(kClosureFunctionBit | kInstanceFunctionBit)) {
// Retrieve the receiver from the context.
const int closure_index =
(function_bits & kGenericFunctionBit) != 0 ? 1 : 0;
const Object& closure = Object::Handle(ArgAt(closure_index));
const Context& context =
Context::Handle(Closure::Cast(closure).context());
return context.At(0);
}
return ArgAt(NumHiddenArgs(function_bits));
}
ObjectPtr NativeArgAt(int index) const {
ASSERT((index >= 0) && (index < NativeArgCount()));
if (index == 0) {
return NativeArg0();
}
int function_bits = FunctionBits::decode(argc_tag_);
const int actual_index = index + NumHiddenArgs(function_bits);
return ArgAt(actual_index);
}
TypeArgumentsPtr NativeTypeArgs() const {
ASSERT(ToGenericFunction());
return TypeArguments::RawCast(ArgAt(0));
}
int NativeTypeArgCount() const {
if (ToGenericFunction()) {
TypeArguments& type_args = TypeArguments::Handle(NativeTypeArgs());
if (type_args.IsNull()) {
// null vector represents infinite list of dynamics
return INT_MAX;
}
return type_args.Length();
}
return 0;
}
AbstractTypePtr NativeTypeArgAt(int index) const {
ASSERT((index >= 0) && (index < NativeTypeArgCount()));
TypeArguments& type_args = TypeArguments::Handle(NativeTypeArgs());
if (type_args.IsNull()) {
// null vector represents infinite list of dynamics
return Type::dynamic_type().ptr();
}
return type_args.TypeAt(index);
}
void SetReturn(const Object& value) const {
ASSERT(thread_->execution_state() == Thread::kThreadInVM);
*retval_ = value.ptr();
}
ObjectPtr ReturnValue() const {
// Tell MemorySanitizer the retval_ was initialized (by generated code).
MSAN_UNPOISON(retval_, kWordSize);
return *retval_;
}
static intptr_t thread_offset() {
return OFFSET_OF(NativeArguments, thread_);
}
static intptr_t argc_tag_offset() {
return OFFSET_OF(NativeArguments, argc_tag_);
}
static intptr_t argv_offset() { return OFFSET_OF(NativeArguments, argv_); }
static intptr_t retval_offset() {
return OFFSET_OF(NativeArguments, retval_);
}
static intptr_t ParameterCountForResolution(const Function& function) {
ASSERT(function.is_old_native());
ASSERT(!function.IsGenerativeConstructor()); // Not supported.
intptr_t count = function.NumParameters();
if (function.is_static() && function.IsClosureFunction()) {
// The closure object is hidden and not accessible from native code.
// However, if the function is an instance closure function, the captured
// receiver located in the context is made accessible in native code at
// index 0, thereby hiding the closure object at index 0.
count--;
}
return count;
}
static int ComputeArgcTag(const Function& function) {
ASSERT(function.is_old_native());
ASSERT(!function.IsGenerativeConstructor()); // Not supported.
int argc = function.NumParameters();
int function_bits = 0;
if (!function.is_static()) {
function_bits |= kInstanceFunctionBit;
}
if (function.IsClosureFunction()) {
function_bits |= kClosureFunctionBit;
}
if (function.IsGeneric()) {
function_bits |= kGenericFunctionBit;
argc++;
}
int tag = ArgcBits::encode(argc);
tag = FunctionBits::update(function_bits, tag);
return tag;
}
private:
enum {
kInstanceFunctionBit = 1,
kClosureFunctionBit = 2,
kGenericFunctionBit = 4,
};
enum ArgcTagBits {
kArgcBit = 0,
kArgcSize = 24,
kFunctionBit = kArgcBit + kArgcSize,
kFunctionSize = 3,
};
class ArgcBits : public BitField<intptr_t, int32_t, kArgcBit, kArgcSize> {};
class FunctionBits
: public BitField<intptr_t, int, kFunctionBit, kFunctionSize> {};
friend class Api;
friend class NativeEntry;
friend class Simulator;
// Since this function is passed an ObjectPtr directly, we need to be
// exceedingly careful when we use it. If there are any other side
// effects in the statement that may cause GC, it could lead to
// bugs.
void SetReturnUnsafe(ObjectPtr value) const {
ASSERT(thread_->execution_state() == Thread::kThreadInVM);
*retval_ = value;
}
// Returns true if the arguments are those of an instance function call.
bool ToInstanceFunction() const {
return (FunctionBits::decode(argc_tag_) & kInstanceFunctionBit) != 0;
}
// Returns true if the arguments are those of a closure function call.
bool ToClosureFunction() const {
return (FunctionBits::decode(argc_tag_) & kClosureFunctionBit) != 0;
}
// Returns true if the arguments are those of a generic function call.
bool ToGenericFunction() const {
return (FunctionBits::decode(argc_tag_) & kGenericFunctionBit) != 0;
}
int NumHiddenArgs(int function_bits) const {
int num_hidden_args = 0;
// For static closure functions, the closure at index 0 is hidden.
// In the instance closure function case, the receiver is accessed from
// the context and the closure at index 0 is hidden, so the apparent
// argument count remains unchanged.
if ((function_bits & kClosureFunctionBit) == kClosureFunctionBit) {
num_hidden_args++;
}
if ((function_bits & kGenericFunctionBit) == kGenericFunctionBit) {
num_hidden_args++;
}
return num_hidden_args;
}
Thread* thread_; // Current thread pointer.
intptr_t argc_tag_; // Encodes argument count and invoked native call type.
ObjectPtr* argv_; // Pointer to an array of arguments to runtime call.
ObjectPtr* retval_; // Pointer to the return value area.
};
} // namespace dart
#endif // RUNTIME_VM_NATIVE_ARGUMENTS_H_