7d8b5becb4
The CallMarshaller is used not only from the compiler, but also
from the FfiCall runtime entry used by the interpreter. Since it
only has one use of the thread's CompilerState, looking up the
TypedData class, move the storage of that class from the compiler
state to the object store and remove this dependency.
TEST=ffi/address_of_array_generated_test
ffi/address_of_cast_test
fii/address_of_struct_generated_test
ffi/address_of_typeddata_generated_test
Fixes: https://github.com/dart-lang/sdk/issues/61913
Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try
Change-Id: Ia4fc5d9ecef370aa9476b998e37cc4ae94ee447f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498563
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
950 lines
35 KiB
C++
950 lines
35 KiB
C++
// Copyright (c) 2020, 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.
|
|
|
|
#include "vm/compiler/ffi/marshaller.h"
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/globals.h"
|
|
#include "vm/class_id.h"
|
|
#include "vm/compiler/ffi/frame_rebase.h"
|
|
#include "vm/compiler/ffi/native_calling_convention.h"
|
|
#include "vm/compiler/ffi/native_location.h"
|
|
#include "vm/compiler/ffi/native_type.h"
|
|
#include "vm/exceptions.h"
|
|
#include "vm/ffi_callback_metadata.h"
|
|
#include "vm/log.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/raw_object.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/tagged_pointer.h"
|
|
|
|
namespace dart {
|
|
|
|
namespace compiler {
|
|
|
|
namespace ffi {
|
|
|
|
// Argument #0 is the function pointer.
|
|
const intptr_t kNativeParamsStartAt = 1;
|
|
|
|
// Representations of the arguments and return value of a C signature function.
|
|
const NativeFunctionType* NativeFunctionTypeFromFunctionType(
|
|
Zone* zone,
|
|
const FunctionType& c_signature,
|
|
const char** error) {
|
|
ASSERT(c_signature.NumOptionalParameters() == 0);
|
|
ASSERT(c_signature.NumOptionalPositionalParameters() == 0);
|
|
ObjectStore* object_store = IsolateGroup::Current()->object_store();
|
|
|
|
const intptr_t num_arguments =
|
|
c_signature.num_fixed_parameters() - kNativeParamsStartAt;
|
|
auto& argument_representations =
|
|
*new ZoneGrowableArray<const NativeType*>(zone, num_arguments);
|
|
AbstractType& arg_type = AbstractType::Handle(zone);
|
|
intptr_t variadic_arguments_index = NativeFunctionType::kNoVariadicArguments;
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
arg_type = c_signature.ParameterTypeAt(i + kNativeParamsStartAt);
|
|
const bool varargs =
|
|
arg_type.type_class() == object_store->ffi_varargs_class();
|
|
if (varargs) {
|
|
arg_type = TypeArguments::Handle(zone, Type::Cast(arg_type).arguments())
|
|
.TypeAt(0);
|
|
variadic_arguments_index = i;
|
|
ASSERT(arg_type.IsRecordType());
|
|
const auto& record_type = RecordType::Cast(arg_type);
|
|
const intptr_t num_fields = record_type.NumFields();
|
|
auto& field_type = AbstractType::Handle(zone);
|
|
for (intptr_t i = 0; i < num_fields; i++) {
|
|
field_type ^= record_type.FieldTypeAt(i);
|
|
const auto rep = NativeType::FromAbstractType(zone, field_type, error);
|
|
if (*error != nullptr) {
|
|
return nullptr;
|
|
}
|
|
argument_representations.Add(rep);
|
|
}
|
|
} else {
|
|
const auto rep = NativeType::FromAbstractType(zone, arg_type, error);
|
|
if (*error != nullptr) {
|
|
return nullptr;
|
|
}
|
|
argument_representations.Add(rep);
|
|
}
|
|
}
|
|
|
|
const auto& result_type =
|
|
AbstractType::Handle(zone, c_signature.result_type());
|
|
const auto result_representation =
|
|
NativeType::FromAbstractType(zone, result_type, error);
|
|
if (*error != nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto result = new (zone)
|
|
NativeFunctionType(argument_representations, *result_representation,
|
|
variadic_arguments_index);
|
|
return result;
|
|
}
|
|
|
|
CallMarshaller* CallMarshaller::FromFunction(Zone* zone,
|
|
const Function& function,
|
|
intptr_t function_params_start_at,
|
|
const FunctionType& c_signature,
|
|
const char** error) {
|
|
DEBUG_ASSERT(function.IsNotTemporaryScopedHandle());
|
|
DEBUG_ASSERT(c_signature.IsNotTemporaryScopedHandle());
|
|
const auto native_function_signature =
|
|
NativeFunctionTypeFromFunctionType(zone, c_signature, error);
|
|
if (*error != nullptr) {
|
|
return nullptr;
|
|
}
|
|
const auto& native_calling_convention =
|
|
NativeCallingConvention::FromSignature(zone, *native_function_signature);
|
|
return new (zone) CallMarshaller(zone, function, function_params_start_at,
|
|
c_signature, native_calling_convention);
|
|
}
|
|
|
|
AbstractTypePtr BaseMarshaller::CType(intptr_t arg_index) const {
|
|
if (arg_index == kResultIndex) {
|
|
return c_signature_.result_type();
|
|
}
|
|
|
|
Zone* zone = Thread::Current()->zone();
|
|
const auto& parameter_types =
|
|
Array::Handle(zone, c_signature_.parameter_types());
|
|
const intptr_t parameter_type_length = parameter_types.Length();
|
|
const intptr_t last_param_index = parameter_type_length - 1;
|
|
const auto& last_arg_type = AbstractType::Handle(
|
|
zone, c_signature_.ParameterTypeAt(last_param_index));
|
|
ObjectStore* object_store = IsolateGroup::Current()->object_store();
|
|
const bool has_varargs =
|
|
last_arg_type.type_class() == object_store->ffi_varargs_class();
|
|
|
|
// Skip #0 argument, the function pointer.
|
|
const intptr_t real_arg_index = arg_index + kNativeParamsStartAt;
|
|
|
|
if (has_varargs && real_arg_index >= last_param_index) {
|
|
// The C-type is nested in a VarArgs.
|
|
const auto& var_args_type_arg = AbstractType::Handle(
|
|
zone, TypeArguments::Handle(zone, Type::Cast(last_arg_type).arguments())
|
|
.TypeAt(0));
|
|
if (var_args_type_arg.IsRecordType()) {
|
|
const intptr_t index_in_record = real_arg_index - last_param_index;
|
|
const auto& record_type = RecordType::Cast(var_args_type_arg);
|
|
ASSERT(index_in_record < record_type.NumFields());
|
|
return record_type.FieldTypeAt(index_in_record);
|
|
} else {
|
|
ASSERT(!var_args_type_arg.IsNull());
|
|
return var_args_type_arg.ptr();
|
|
}
|
|
}
|
|
|
|
ASSERT(!AbstractType::Handle(c_signature_.ParameterTypeAt(real_arg_index))
|
|
.IsNull());
|
|
return c_signature_.ParameterTypeAt(real_arg_index);
|
|
}
|
|
|
|
AbstractTypePtr BaseMarshaller::DartType(intptr_t arg_index) const {
|
|
if (arg_index == kResultIndex) {
|
|
return dart_signature_.result_type();
|
|
}
|
|
const intptr_t real_arg_index = arg_index + dart_signature_params_start_at_;
|
|
ASSERT(!Array::Handle(dart_signature_.parameter_types()).IsNull());
|
|
ASSERT(real_arg_index <
|
|
Array::Handle(dart_signature_.parameter_types()).Length());
|
|
ASSERT(!AbstractType::Handle(dart_signature_.ParameterTypeAt(real_arg_index))
|
|
.IsNull());
|
|
return dart_signature_.ParameterTypeAt(real_arg_index);
|
|
}
|
|
|
|
bool BaseMarshaller::IsPointerCType(intptr_t arg_index) const {
|
|
return AbstractType::Handle(zone_, CType(arg_index)).type_class_id() ==
|
|
kPointerCid;
|
|
}
|
|
|
|
bool BaseMarshaller::IsPointerDartType(intptr_t arg_index) const {
|
|
return AbstractType::Handle(zone_, DartType(arg_index)).type_class_id() ==
|
|
kPointerCid;
|
|
}
|
|
|
|
bool BaseMarshaller::IsPointerPointer(intptr_t arg_index) const {
|
|
if (dart_signature_.parameter_types() == Array::null()) {
|
|
// TODO(https://dartbug.com/54173): BuildGraphOfSyncFfiCallback provides a
|
|
// function object with its type arguments not initialized.
|
|
return IsPointerCType(arg_index);
|
|
}
|
|
return IsPointerDartType(arg_index) && IsPointerCType(arg_index);
|
|
}
|
|
|
|
bool BaseMarshaller::IsTypedDataPointer(intptr_t arg_index) const {
|
|
if (!IsPointerCType(arg_index)) {
|
|
return false;
|
|
}
|
|
if (IsHandleCType(arg_index)) {
|
|
return false;
|
|
}
|
|
|
|
if (dart_signature_.parameter_types() == Array::null()) {
|
|
// TODO(https://dartbug.com/54173): BuildGraphOfSyncFfiCallback provides a
|
|
// function object with its type arguments not initialized. Change this
|
|
// to an assert when addressing that issue.
|
|
return false;
|
|
}
|
|
|
|
const auto& type = AbstractType::Handle(zone_, DartType(arg_index));
|
|
auto* const object_store = Thread::Current()->isolate_group()->object_store();
|
|
auto& typed_data_cls = Class::Handle(zone_, object_store->typed_data_class());
|
|
if (typed_data_cls.IsNull()) {
|
|
const auto& lib = Library::Handle(zone_, Library::TypedDataLibrary());
|
|
typed_data_cls = lib.LookupClass(Symbols::TypedData());
|
|
ASSERT(!typed_data_cls.IsNull());
|
|
object_store->set_typed_data_class(typed_data_cls);
|
|
}
|
|
return type.type_class() == typed_data_cls.ptr();
|
|
}
|
|
|
|
static bool IsCompound(Zone* zone, const AbstractType& type) {
|
|
ObjectStore* object_store =
|
|
Thread::Current()->isolate_group()->object_store();
|
|
auto& cls = Class::Handle(zone, type.type_class());
|
|
if ((object_store->ffi_compound_class() == cls.ptr()) ||
|
|
(object_store->ffi_array_class() == cls.ptr())) {
|
|
return true;
|
|
}
|
|
cls ^= cls.SuperClass();
|
|
if ((object_store->ffi_struct_class() == cls.ptr()) ||
|
|
(object_store->ffi_union_class() == cls.ptr())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BaseMarshaller::IsCompoundPointer(intptr_t arg_index) const {
|
|
if (!IsPointerCType(arg_index)) {
|
|
return false;
|
|
}
|
|
if (dart_signature_.parameter_types() == Array::null()) {
|
|
// TODO(https://dartbug.com/54173): BuildGraphOfSyncFfiCallback provides a
|
|
// function object with its type arguments not initialized.
|
|
return false;
|
|
}
|
|
|
|
const auto& dart_type = AbstractType::Handle(zone_, DartType(arg_index));
|
|
return IsCompound(this->zone_, dart_type);
|
|
}
|
|
|
|
bool BaseMarshaller::IsHandleCType(intptr_t arg_index) const {
|
|
return AbstractType::Handle(zone_, CType(arg_index)).type_class_id() ==
|
|
kFfiHandleCid;
|
|
}
|
|
|
|
bool BaseMarshaller::IsBool(intptr_t arg_index) const {
|
|
return AbstractType::Handle(zone_, CType(arg_index)).type_class_id() ==
|
|
kFfiBoolCid;
|
|
}
|
|
|
|
// Keep consistent with Function::FfiCSignatureReturnsStruct.
|
|
bool BaseMarshaller::IsCompoundCType(intptr_t arg_index) const {
|
|
const auto& c_type = AbstractType::Handle(zone_, CType(arg_index));
|
|
return IsCompound(this->zone_, c_type);
|
|
}
|
|
|
|
bool BaseMarshaller::ContainsHandles() const {
|
|
return c_signature_.ContainsHandles();
|
|
}
|
|
|
|
intptr_t BaseMarshaller::NumArgumentDefinitions() const {
|
|
intptr_t total = 0;
|
|
for (intptr_t i = 0; i < num_args(); i++) {
|
|
total += NumDefinitions(i);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
intptr_t BaseMarshaller::NumDefinitions(intptr_t arg_index) const {
|
|
if (ArgumentIndexIsReturn(arg_index)) {
|
|
return NumReturnDefinitions();
|
|
}
|
|
|
|
const auto& loc = Location(arg_index);
|
|
const auto& type = loc.payload_type();
|
|
|
|
if (IsCompoundPointer(arg_index)) {
|
|
// typed data base and offset.
|
|
return 2;
|
|
}
|
|
if (type.IsPrimitive()) {
|
|
// All non-struct arguments are 1 definition in IL. Even 64 bit values
|
|
// on 32 bit architectures.
|
|
return 1;
|
|
}
|
|
|
|
ASSERT(type.IsCompound());
|
|
ASSERT(!loc.IsPointerToMemory()); // Handled in overrides.
|
|
if (loc.IsMultiple()) {
|
|
// One IL definition for every nested location.
|
|
const auto& multiple = loc.AsMultiple();
|
|
return multiple.locations().length();
|
|
}
|
|
|
|
ASSERT(loc.IsStack());
|
|
// For stack, word size definitions in IL. In FFI calls passed into the
|
|
// native call, in FFI callbacks read in separate NativeParams.
|
|
const intptr_t size_in_bytes = type.SizeInBytes();
|
|
const intptr_t num_defs =
|
|
Utils::RoundUp(size_in_bytes, compiler::target::kWordSize) /
|
|
compiler::target::kWordSize;
|
|
return num_defs;
|
|
}
|
|
|
|
intptr_t CallMarshaller::NumDefinitions(intptr_t arg_index) const {
|
|
if (!ArgumentIndexIsReturn(arg_index)) {
|
|
const auto& loc = Location(arg_index);
|
|
const auto& type = loc.payload_type();
|
|
if (type.IsCompound() && loc.IsPointerToMemory()) {
|
|
// For FFI calls, pass in TypedDataBase and offsetInBytes in IL, and copy
|
|
// contents to stack and pass pointer in right location in MC.
|
|
return 2;
|
|
}
|
|
}
|
|
return BaseMarshaller::NumDefinitions(arg_index);
|
|
}
|
|
|
|
intptr_t CallbackMarshaller::NumDefinitions(intptr_t arg_index) const {
|
|
if (!ArgumentIndexIsReturn(arg_index)) {
|
|
const auto& loc = Location(arg_index);
|
|
const auto& type = loc.payload_type();
|
|
if (type.IsCompound() && loc.IsPointerToMemory()) {
|
|
// For FFI callbacks, get the pointer in a NativeParameter and construct
|
|
// the TypedDataBase in IL (always offset in bytes 0).
|
|
return 1;
|
|
}
|
|
}
|
|
return BaseMarshaller::NumDefinitions(arg_index);
|
|
}
|
|
|
|
intptr_t CallMarshaller::NumReturnDefinitions() const {
|
|
// An FFICall is a Definition, and Definitions currently only have one
|
|
// return value.
|
|
//
|
|
// For compound returns, the IL generated by the flow graph builder allocates
|
|
// a TypedDataBase object that is passed into the FfiCall. The generated
|
|
// machine code for the FfiCall instruction then fills in the contents.
|
|
// After the call, the generated IL wraps the TypedDataBase object in a
|
|
// Compound object with an offset in bytes of 0.
|
|
return 1;
|
|
}
|
|
|
|
intptr_t CallbackMarshaller::NumReturnDefinitions() const {
|
|
const auto& loc = Location(kResultIndex);
|
|
|
|
if (loc.IsMultiple()) {
|
|
const auto& type = loc.payload_type();
|
|
ASSERT(type.IsCompound());
|
|
// For multiple locations, some native locations cannot be expressed as
|
|
// Locations, which means the flow graph builder cannot generate appropriate
|
|
// IL for those cases.
|
|
//
|
|
// Instead, the flow graph builder generates IL to extract the
|
|
// _typedDataBase and _offsetInBytes fields of the Dart value and passes
|
|
// those into the NativeReturn instruction as separate arguments. The
|
|
// generated machine code for the NativeReturn instruction then
|
|
// appropriately copies the contents to a non-GC-managed block of memory. A
|
|
// pointer to that block of memory is returned to the C code.
|
|
return 2;
|
|
}
|
|
|
|
// If it's a compound and the native ABI is passing a pointer, copy to it in
|
|
// IL. If non-compound, also 1 definition. If it's a primitive, the flow graph
|
|
// builder generates IL to create an appropriate Dart value from the single
|
|
// value returned from C.
|
|
return 1;
|
|
}
|
|
|
|
bool BaseMarshaller::ArgumentIndexIsReturn(intptr_t arg_index) const {
|
|
ASSERT(arg_index == kResultIndex || arg_index >= 0);
|
|
return arg_index == kResultIndex;
|
|
}
|
|
|
|
// Definitions in return value count down.
|
|
bool BaseMarshaller::DefinitionIndexIsReturn(intptr_t def_index_global) const {
|
|
return def_index_global <= kResultIndex;
|
|
}
|
|
|
|
intptr_t BaseMarshaller::ArgumentIndex(intptr_t def_index_global) const {
|
|
if (DefinitionIndexIsReturn(def_index_global)) {
|
|
const intptr_t def = DefinitionInArgument(def_index_global, kResultIndex);
|
|
ASSERT(def < NumReturnDefinitions());
|
|
return kResultIndex;
|
|
}
|
|
ASSERT(def_index_global < NumArgumentDefinitions());
|
|
intptr_t defs = 0;
|
|
intptr_t arg_index = 0;
|
|
for (; arg_index < num_args(); arg_index++) {
|
|
defs += NumDefinitions(arg_index);
|
|
if (defs > def_index_global) {
|
|
return arg_index;
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
intptr_t BaseMarshaller::FirstDefinitionIndex(intptr_t arg_index) const {
|
|
if (arg_index <= kResultIndex) {
|
|
return kResultIndex;
|
|
}
|
|
ASSERT(arg_index < num_args());
|
|
intptr_t num_defs = 0;
|
|
for (intptr_t i = 0; i < arg_index; i++) {
|
|
num_defs += NumDefinitions(i);
|
|
}
|
|
return num_defs;
|
|
}
|
|
|
|
intptr_t BaseMarshaller::DefinitionInArgument(intptr_t def_index_global,
|
|
intptr_t arg_index) const {
|
|
if (ArgumentIndexIsReturn(arg_index)) {
|
|
// Counting down for return definitions.
|
|
const intptr_t def = kResultIndex - def_index_global;
|
|
ASSERT(def < NumReturnDefinitions());
|
|
return def;
|
|
} else {
|
|
// Counting up for arguments in consecutive order.
|
|
const intptr_t def = def_index_global - FirstDefinitionIndex(arg_index);
|
|
ASSERT(def < NumArgumentDefinitions());
|
|
return def;
|
|
}
|
|
}
|
|
|
|
intptr_t BaseMarshaller::DefinitionIndex(intptr_t def_index_in_arg,
|
|
intptr_t arg_index) const {
|
|
ASSERT(def_index_in_arg < NumDefinitions(arg_index));
|
|
if (ArgumentIndexIsReturn(arg_index)) {
|
|
return kResultIndex - def_index_in_arg;
|
|
} else {
|
|
return FirstDefinitionIndex(arg_index) + def_index_in_arg;
|
|
}
|
|
}
|
|
|
|
static Representation SelectRepresentationInIL(Zone* zone,
|
|
const NativeLocation& location) {
|
|
if (location.container_type().IsInt() && location.payload_type().IsFloat()) {
|
|
// IL can only pass integers to integer Locations, so pass as integer if
|
|
// the Location requires it to be an integer.
|
|
return location.container_type().AsRepresentationOverApprox(zone);
|
|
}
|
|
// Representations do not support 8 or 16 bit ints, over approximate to 32
|
|
// bits.
|
|
return location.payload_type().AsRepresentationOverApprox(zone);
|
|
}
|
|
|
|
Representation BaseMarshaller::RepInDart(intptr_t arg_index) const {
|
|
// This should never be called on Pointers or Handles, which are specially
|
|
// handled during marshalling/unmarshalling.
|
|
ASSERT(!IsHandleCType(arg_index));
|
|
ASSERT(!IsPointerPointer(arg_index));
|
|
return Location(arg_index).payload_type().AsRepresentationOverApprox(zone_);
|
|
}
|
|
|
|
// Implemented partially in BaseMarshaller because most Representations are
|
|
// the same in Calls and Callbacks.
|
|
Representation BaseMarshaller::RepInFfiCall(intptr_t def_index_global) const {
|
|
intptr_t arg_index = ArgumentIndex(def_index_global);
|
|
|
|
// Handled appropriately in the subclasses.
|
|
ASSERT(!IsHandleCType(arg_index));
|
|
|
|
// The IL extracts the address stored in the Pointer object as an untagged
|
|
// pointer before passing it to C, and creates a new Pointer object to store
|
|
// the received untagged pointer when receiving a pointer from C.
|
|
if (IsPointerPointer(arg_index)) return kUntagged;
|
|
|
|
const auto& location = Location(arg_index);
|
|
if (location.container_type().IsPrimitive()) {
|
|
return SelectRepresentationInIL(zone_, location);
|
|
}
|
|
ASSERT(location.container_type().IsCompound());
|
|
|
|
if (location.IsStack()) {
|
|
// Split the struct in architecture size chunks.
|
|
return kUnboxedWord;
|
|
}
|
|
|
|
if (location.IsMultiple()) {
|
|
const intptr_t def_index_in_arg =
|
|
DefinitionInArgument(def_index_global, arg_index);
|
|
const auto& def_loc =
|
|
*(location.AsMultiple().locations()[def_index_in_arg]);
|
|
return SelectRepresentationInIL(zone_, def_loc);
|
|
}
|
|
|
|
UNREACHABLE(); // Implemented in subclasses.
|
|
}
|
|
|
|
static const intptr_t kTypedDataBaseIndex = 0;
|
|
static const intptr_t kOffsetInBytesIndex = 1;
|
|
|
|
Representation CallMarshaller::RepInFfiCall(intptr_t def_index_global) const {
|
|
intptr_t arg_index = ArgumentIndex(def_index_global);
|
|
if (IsHandleCType(arg_index)) {
|
|
// For FfiCall arguments, the FfiCall instruction takes a tagged pointer
|
|
// from the IL. (It then creates a handle on the stack and passes a
|
|
// pointer to the newly allocated handle to C.)
|
|
//
|
|
// For FfiCall returns, FfiCall returns the untagged pointer to the handle
|
|
// to the IL, which then extracts the ptr field of the handle to retrieve
|
|
// the tagged pointer.
|
|
return ArgumentIndexIsReturn(arg_index) ? kUntagged : kTagged;
|
|
}
|
|
if (ArgumentIndexIsReturn(arg_index) && ReturnsCompound()) {
|
|
// The IL creates a TypedData object which is stored on the stack, and the
|
|
// FfiCall copies the compound value, however it is returned into that
|
|
// TypedData object. In order to make the return value of the definition
|
|
// defined, the same TypedData object is returned from the FfiCall.
|
|
return kTagged;
|
|
}
|
|
const auto& location = Location(arg_index);
|
|
if (location.IsPointerToMemory() || IsCompoundPointer(arg_index) ||
|
|
IsTypedDataPointer(arg_index)) {
|
|
// For arguments, the compound data being passed as a pointer is first
|
|
// collected into a TypedData object by the IL, and that object is what is
|
|
// passed to the FfiCall instruction. (The machine code generated by
|
|
// FfiCall handles copying the data into non-GC-moveable memory and
|
|
// passing a pointer to that memory to the C code.)
|
|
const intptr_t def_index_in_arg =
|
|
def_index_global - FirstDefinitionIndex(arg_index);
|
|
if (def_index_in_arg == kTypedDataBaseIndex) {
|
|
// The TypedDataBase object is managed by the GC, so the payload address
|
|
// cannot be eagerly extracted in the IL, as that would create an
|
|
// unsafe untagged address as an input to a GC-triggering instruction.
|
|
return kTagged;
|
|
} else {
|
|
ASSERT_EQUAL(def_index_in_arg, kOffsetInBytesIndex);
|
|
ASSERT(!IsTypedDataPointer(arg_index));
|
|
return kUnboxedUword;
|
|
}
|
|
}
|
|
return BaseMarshaller::RepInFfiCall(def_index_global);
|
|
}
|
|
|
|
Representation CallbackMarshaller::RepInFfiCall(
|
|
intptr_t def_index_global) const {
|
|
intptr_t arg_index = ArgumentIndex(def_index_global);
|
|
if (IsHandleCType(arg_index)) {
|
|
// Dart objects are passed to C as untagged pointers to newly created
|
|
// handles in the IL, and the ptr field of untagged pointers to handles are
|
|
// extracted when the IL receives handles from C code.
|
|
return kUntagged;
|
|
}
|
|
const auto& location = Location(arg_index);
|
|
if (location.IsPointerToMemory()) {
|
|
// The IL gets an untagged pointer to memory both for arguments and for
|
|
// returns. If this is an argument, then the IL creates a Dart
|
|
// representation of the compound object from the pointed at memory.
|
|
// For returns, the IL copies the data from the compound object into
|
|
// the memory being pointed at before returning to C.
|
|
return kUntagged;
|
|
}
|
|
if (ArgumentIndexIsReturn(arg_index) && location.IsMultiple()) {
|
|
// To return a compound object broken up over multiple native locations,
|
|
// the IL loads the compound object into a single TypedData object and
|
|
// passes that TypedData object to NativeReturn, which handles extracting
|
|
// the data to the appropriate native locations.
|
|
return kTagged;
|
|
}
|
|
return BaseMarshaller::RepInFfiCall(def_index_global);
|
|
}
|
|
|
|
void BaseMarshaller::RepsInFfiCall(intptr_t arg_index,
|
|
GrowableArray<Representation>* out) const {
|
|
const intptr_t num_definitions = NumDefinitions(arg_index);
|
|
const intptr_t first_def = FirstDefinitionIndex(arg_index);
|
|
for (int i = 0; i < num_definitions; i++) {
|
|
out->Add(RepInFfiCall(first_def + i));
|
|
}
|
|
}
|
|
|
|
// Helper method for `LocInFfiCall` to turn a stack location into either any
|
|
// location or a pair of two any locations.
|
|
static Location ConvertToAnyLocation(const NativeStackLocation& loc,
|
|
Representation rep_in_ffi_call) {
|
|
// Floating point values are never split: they are either in a single "FPU"
|
|
// register or a contiguous 64-bit slot on the stack. Unboxed 64-bit integer
|
|
// values, in contrast, can be split between any two registers on a 32-bit
|
|
// system.
|
|
//
|
|
// There is an exception for iOS and Android 32-bit ARM, where
|
|
// floating-point values are treated as integers as far as the calling
|
|
// convention is concerned. However, the representation of these arguments
|
|
// are set to kUnboxedInt32 or kUnboxedInt64 already, so we don't have to
|
|
// account for that here.
|
|
const bool is_atomic =
|
|
rep_in_ffi_call == kUnboxedDouble || rep_in_ffi_call == kUnboxedFloat;
|
|
|
|
if (loc.payload_type().IsPrimitive() &&
|
|
loc.payload_type().SizeInBytes() == 2 * compiler::target::kWordSize &&
|
|
!is_atomic) {
|
|
return Location::Pair(Location::Any(), Location::Any());
|
|
}
|
|
return Location::Any();
|
|
}
|
|
|
|
static Location SelectFpuLocationInIL(Zone* zone,
|
|
const NativeLocation& location) {
|
|
ASSERT((location.IsFpuRegisters()));
|
|
#if defined(TARGET_ARCH_ARM)
|
|
// Only pin FPU register if it is the lowest bits.
|
|
const auto& fpu_loc = location.AsFpuRegisters();
|
|
if (fpu_loc.IsLowestBits()) {
|
|
return fpu_loc.WidenToQFpuRegister(zone).AsLocation();
|
|
}
|
|
return Location::Any();
|
|
#endif // defined(TARGET_ARCH_ARM)
|
|
|
|
return location.AsLocation();
|
|
}
|
|
|
|
Location CallMarshaller::LocInFfiCall(intptr_t def_index_global) const {
|
|
const intptr_t arg_index = ArgumentIndex(def_index_global);
|
|
const NativeLocation& loc = this->Location(arg_index);
|
|
|
|
if (ArgumentIndexIsReturn(arg_index)) {
|
|
if (loc.IsRegisters() || loc.IsFpuRegisters()) {
|
|
return loc.AsLocation();
|
|
}
|
|
ASSERT(ReturnsCompound());
|
|
// No location at all, because we store into TypedData passed to the
|
|
// FfiCall instruction. But we have to supply a location.
|
|
return Location::RegisterLocation(CallingConventions::kReturnReg);
|
|
}
|
|
|
|
// Force all handles to be Stack locations.
|
|
if (IsHandleCType(arg_index)) {
|
|
return Location::RequiresStack();
|
|
}
|
|
|
|
if (loc.IsMultiple()) {
|
|
const intptr_t def_index_in_arg =
|
|
def_index_global - FirstDefinitionIndex(arg_index);
|
|
const auto& def_loc = *(loc.AsMultiple().locations()[def_index_in_arg]);
|
|
if (def_loc.IsStack()) {
|
|
// Don't pin stack locations, they need to be moved anyway.
|
|
return ConvertToAnyLocation(def_loc.AsStack(),
|
|
RepInFfiCall(def_index_global));
|
|
}
|
|
|
|
if (def_loc.IsFpuRegisters()) {
|
|
return SelectFpuLocationInIL(zone_, def_loc);
|
|
}
|
|
|
|
return def_loc.AsLocation();
|
|
}
|
|
|
|
if (loc.IsPointerToMemory()) {
|
|
const intptr_t def_index_in_arg =
|
|
def_index_global - FirstDefinitionIndex(arg_index);
|
|
if (def_index_in_arg == kTypedDataBaseIndex) {
|
|
const auto& pointer_location = loc.AsPointerToMemory().pointer_location();
|
|
if (pointer_location.IsStack()) {
|
|
// Don't pin stack locations, they need to be moved anyway.
|
|
return ConvertToAnyLocation(pointer_location.AsStack(),
|
|
RepInFfiCall(def_index_global));
|
|
}
|
|
return pointer_location.AsLocation();
|
|
} else {
|
|
ASSERT_EQUAL(def_index_in_arg, kOffsetInBytesIndex);
|
|
return Location::Any();
|
|
}
|
|
}
|
|
|
|
if (IsCompoundPointer(arg_index)) {
|
|
const intptr_t def_index_in_arg =
|
|
def_index_global - FirstDefinitionIndex(arg_index);
|
|
if (def_index_in_arg == kOffsetInBytesIndex) {
|
|
// The typed data is passed in the location from the calling convention.
|
|
// The offset in bytes can be passed in any location.
|
|
return Location::Any();
|
|
}
|
|
}
|
|
|
|
if (loc.IsStack()) {
|
|
return ConvertToAnyLocation(loc.AsStack(), RepInFfiCall(def_index_global));
|
|
}
|
|
|
|
if (loc.IsFpuRegisters()) {
|
|
return SelectFpuLocationInIL(zone_, loc);
|
|
}
|
|
|
|
if (loc.IsBoth()) {
|
|
const auto& fpu_reg_loc = loc.AsBoth().location(0).AsFpuRegisters();
|
|
return SelectFpuLocationInIL(zone_, fpu_reg_loc);
|
|
}
|
|
|
|
ASSERT(loc.IsRegisters());
|
|
return loc.AsLocation();
|
|
}
|
|
|
|
bool CallMarshaller::ReturnsCompound() const {
|
|
return IsCompoundCType(compiler::ffi::kResultIndex);
|
|
}
|
|
|
|
intptr_t CallMarshaller::CompoundReturnSizeInBytes() const {
|
|
ASSERT(ReturnsCompound());
|
|
return Utils::RoundUp(
|
|
Location(compiler::ffi::kResultIndex).payload_type().SizeInBytes(),
|
|
compiler::target::kWordSize);
|
|
}
|
|
|
|
// Const to be able to look up the `RequiredStackSpaceInBytes` in
|
|
// `PassByPointerStackOffset`.
|
|
const intptr_t kAfterLastArgumentIndex = kIntptrMax;
|
|
|
|
intptr_t CallMarshaller::PassByPointerStackOffset(intptr_t arg_index) const {
|
|
ASSERT(arg_index == kResultIndex ||
|
|
(arg_index >= 0 && arg_index < num_args()) ||
|
|
arg_index == kAfterLastArgumentIndex);
|
|
|
|
intptr_t stack_offset = 0;
|
|
|
|
// First the native arguments are on the stack.
|
|
// This is governed by the native ABI, the rest we can chose freely.
|
|
stack_offset += native_calling_convention_.StackTopInBytes();
|
|
#if (defined(DART_TARGET_OS_MACOS_IOS) || defined(DART_TARGET_OS_MACOS)) && \
|
|
defined(TARGET_ARCH_ARM64)
|
|
// Add extra padding for possibly non stack-aligned word-size writes.
|
|
// TODO(https://dartbug.com/48806): Re-engineer the moves to not over-
|
|
// approximate struct sizes on stack.
|
|
stack_offset += 4;
|
|
#endif
|
|
stack_offset = Utils::RoundUp(stack_offset, compiler::target::kWordSize);
|
|
if (arg_index == kResultIndex) {
|
|
return stack_offset;
|
|
}
|
|
|
|
// Then save space for the result.
|
|
const auto& result_location = Location(compiler::ffi::kResultIndex);
|
|
if (result_location.IsPointerToMemory()) {
|
|
stack_offset += result_location.payload_type().SizeInBytes();
|
|
stack_offset = Utils::RoundUp(stack_offset, compiler::target::kWordSize);
|
|
}
|
|
|
|
// And finally put the arguments on the stack that are passed by pointer.
|
|
for (int i = 0; i < num_args(); i++) {
|
|
if (arg_index == i) {
|
|
return stack_offset;
|
|
}
|
|
const auto& arg_location = Location(i);
|
|
if (arg_location.IsPointerToMemory()) {
|
|
stack_offset += arg_location.payload_type().SizeInBytes();
|
|
stack_offset = Utils::RoundUp(stack_offset, compiler::target::kWordSize);
|
|
}
|
|
}
|
|
|
|
// The total stack space we need.
|
|
ASSERT(arg_index == kAfterLastArgumentIndex);
|
|
return stack_offset;
|
|
}
|
|
|
|
intptr_t CallMarshaller::RequiredStackSpaceInBytes() const {
|
|
return PassByPointerStackOffset(kAfterLastArgumentIndex);
|
|
}
|
|
|
|
// This classes translates the ABI location of arguments into the locations they
|
|
// will inhabit after entry-frame setup in the invocation of a native callback.
|
|
//
|
|
// Native -> Dart callbacks must push all the arguments before executing any
|
|
// Dart code because the reading the Thread from TLS requires calling a native
|
|
// stub, and the argument registers are volatile on all ABIs we support.
|
|
//
|
|
// To avoid complicating initial definitions, all callback arguments are read
|
|
// off the stack from their pushed locations, so this class updates the argument
|
|
// positions to account for this.
|
|
//
|
|
// See 'NativeEntryInstr::EmitNativeCode' for details.
|
|
class CallbackArgumentTranslator : public ValueObject {
|
|
public:
|
|
static NativeLocations& TranslateArgumentLocations(
|
|
Zone* zone,
|
|
const NativeLocations& argument_locations,
|
|
const NativeLocation& return_loc) {
|
|
const bool treat_return_loc = return_loc.IsPointerToMemory();
|
|
|
|
auto& pushed_locs = *(new (zone) NativeLocations(
|
|
argument_locations.length() + (treat_return_loc ? 1 : 0)));
|
|
|
|
CallbackArgumentTranslator translator;
|
|
for (intptr_t i = 0, n = argument_locations.length(); i < n; i++) {
|
|
translator.AllocateArgument(*argument_locations[i]);
|
|
}
|
|
if (treat_return_loc) {
|
|
translator.AllocateArgument(return_loc);
|
|
}
|
|
for (intptr_t i = 0, n = argument_locations.length(); i < n; ++i) {
|
|
pushed_locs.Add(
|
|
&translator.TranslateArgument(zone, *argument_locations[i]));
|
|
}
|
|
if (treat_return_loc) {
|
|
pushed_locs.Add(&translator.TranslateArgument(zone, return_loc));
|
|
}
|
|
|
|
return pushed_locs;
|
|
}
|
|
|
|
private:
|
|
void AllocateArgument(const NativeLocation& arg) {
|
|
if (arg.IsStack()) return;
|
|
|
|
if (arg.IsRegisters()) {
|
|
argument_slots_required_ += arg.AsRegisters().num_regs();
|
|
} else if (arg.IsFpuRegisters()) {
|
|
argument_slots_required_ += 8 / target::kWordSize;
|
|
} else if (arg.IsPointerToMemory()) {
|
|
if (arg.AsPointerToMemory().pointer_location().IsRegisters()) {
|
|
argument_slots_required_ += 1;
|
|
}
|
|
} else if (arg.IsMultiple()) {
|
|
const auto& multiple = arg.AsMultiple();
|
|
for (intptr_t i = 0; i < multiple.locations().length(); i++) {
|
|
AllocateArgument(*multiple.locations().At(i));
|
|
}
|
|
} else {
|
|
ASSERT(arg.IsBoth());
|
|
const auto& both = arg.AsBoth();
|
|
AllocateArgument(both.location(0));
|
|
}
|
|
}
|
|
|
|
const NativeLocation& TranslateArgument(Zone* zone,
|
|
const NativeLocation& arg) {
|
|
if (arg.IsStack()) {
|
|
// Add extra slots after the saved arguments for the return address and
|
|
// frame pointer of the dummy arguments frame, which will be between the
|
|
// saved argument registers and stack arguments. Also add slots for the
|
|
// shadow space if present (factored into
|
|
// kCallbackSlotsBeforeSavedArguments).
|
|
//
|
|
// Finally, for NativeCallbackTrampolines, factor in the extra stack space
|
|
// corresponding to those trampolines' frames (above the entry frame).
|
|
const intptr_t stack_delta =
|
|
kCallbackSlotsBeforeSavedArguments +
|
|
FfiCallbackMetadata::kNativeCallbackTrampolineStackDelta;
|
|
FrameRebase rebase(
|
|
zone,
|
|
/*old_base=*/SPREG, /*new_base=*/SPREG,
|
|
/*stack_delta_in_bytes=*/(argument_slots_required_ + stack_delta) *
|
|
compiler::target::kWordSize);
|
|
return rebase.Rebase(arg);
|
|
}
|
|
|
|
if (arg.IsRegisters()) {
|
|
const auto& result = *new (zone) NativeStackLocation(
|
|
arg.payload_type(), arg.container_type(), SPREG,
|
|
argument_slots_used_ * compiler::target::kWordSize);
|
|
argument_slots_used_ += arg.AsRegisters().num_regs();
|
|
return result;
|
|
}
|
|
|
|
if (arg.IsFpuRegisters()) {
|
|
const auto& result = *new (zone) NativeStackLocation(
|
|
arg.payload_type(), arg.container_type(), SPREG,
|
|
argument_slots_used_ * compiler::target::kWordSize);
|
|
argument_slots_used_ += 8 / target::kWordSize;
|
|
return result;
|
|
}
|
|
|
|
if (arg.IsPointerToMemory()) {
|
|
const auto& pointer_loc = arg.AsPointerToMemory().pointer_location();
|
|
const auto& pointer_ret_loc =
|
|
arg.AsPointerToMemory().pointer_return_location();
|
|
const auto& pointer_translated = TranslateArgument(zone, pointer_loc);
|
|
return *new (zone) PointerToMemoryLocation(
|
|
pointer_translated, pointer_ret_loc, arg.payload_type().AsCompound());
|
|
}
|
|
|
|
if (arg.IsMultiple()) {
|
|
const auto& multiple = arg.AsMultiple();
|
|
NativeLocations& multiple_locations =
|
|
*new (zone) NativeLocations(multiple.locations().length());
|
|
for (intptr_t i = 0; i < multiple.locations().length(); i++) {
|
|
multiple_locations.Add(
|
|
&TranslateArgument(zone, *multiple.locations().At(i)));
|
|
}
|
|
return *new (zone) MultipleNativeLocations(
|
|
multiple.payload_type().AsCompound(), multiple_locations);
|
|
}
|
|
|
|
ASSERT(arg.IsBoth());
|
|
const auto& both = arg.AsBoth();
|
|
// We only need one.
|
|
return TranslateArgument(zone, both.location(0));
|
|
}
|
|
|
|
intptr_t argument_slots_used_ = 0;
|
|
intptr_t argument_slots_required_ = 0;
|
|
};
|
|
|
|
CallbackMarshaller* CallbackMarshaller::FromFunction(Zone* zone,
|
|
const Function& function,
|
|
const char** error) {
|
|
DEBUG_ASSERT(function.IsNotTemporaryScopedHandle());
|
|
const auto& c_signature =
|
|
FunctionType::ZoneHandle(zone, function.FfiCSignature());
|
|
const auto native_function_signature =
|
|
NativeFunctionTypeFromFunctionType(zone, c_signature, error);
|
|
if (*error != nullptr) {
|
|
return nullptr;
|
|
}
|
|
const auto& native_calling_convention =
|
|
NativeCallingConvention::FromSignature(zone, *native_function_signature);
|
|
const auto& callback_locs =
|
|
CallbackArgumentTranslator::TranslateArgumentLocations(
|
|
zone, native_calling_convention.argument_locations(),
|
|
native_calling_convention.return_location());
|
|
return new (zone) CallbackMarshaller(
|
|
zone, function, c_signature, native_calling_convention, callback_locs);
|
|
}
|
|
|
|
const NativeLocation& CallbackMarshaller::NativeLocationOfNativeParameter(
|
|
intptr_t def_index) const {
|
|
const intptr_t arg_index = ArgumentIndex(def_index);
|
|
if (arg_index == kResultIndex) {
|
|
const auto& result_loc = Location(arg_index);
|
|
if (result_loc.IsPointerToMemory()) {
|
|
// If it's a pointer we return it in the last.
|
|
return *callback_locs_.At(callback_locs_.length() - 1);
|
|
}
|
|
// The other return types are not translated.
|
|
return result_loc;
|
|
}
|
|
|
|
// Check that we only have stack arguments.
|
|
const auto& loc = *callback_locs_.At(arg_index);
|
|
ASSERT(loc.IsStack() || loc.IsPointerToMemory() || loc.IsMultiple());
|
|
if (loc.IsStack()) {
|
|
ASSERT(loc.AsStack().base_register() == SPREG);
|
|
if (loc.payload_type().IsPrimitive()) {
|
|
return loc;
|
|
}
|
|
const intptr_t index = DefinitionInArgument(def_index, arg_index);
|
|
const intptr_t count = NumDefinitions(arg_index);
|
|
return loc.Split(zone_, count, index);
|
|
} else if (loc.IsPointerToMemory()) {
|
|
const auto& pointer_loc = loc.AsPointerToMemory().pointer_location();
|
|
ASSERT(pointer_loc.IsStack() &&
|
|
pointer_loc.AsStack().base_register() == SPREG);
|
|
return loc;
|
|
}
|
|
const auto& multiple = loc.AsMultiple();
|
|
const intptr_t index = DefinitionInArgument(def_index, arg_index);
|
|
const auto& multi_loc = *multiple.locations().At(index);
|
|
ASSERT(multi_loc.IsStack() && multi_loc.AsStack().base_register() == SPREG);
|
|
return multi_loc;
|
|
}
|
|
|
|
} // namespace ffi
|
|
|
|
} // namespace compiler
|
|
|
|
} // namespace dart
|