225a301637
There are minor fixes for dartkb and windows. The original revision is in patchset 1. Change-Id: I9ab6e5fdb33fb4e84ea520c283fe94323616a8ce Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103129 Commit-Queue: Samir Jindel <sjindel@google.com> Auto-Submit: Samir Jindel <sjindel@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
557 lines
18 KiB
C++
557 lines
18 KiB
C++
// Copyright (c) 2019, 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.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "platform/globals.h"
|
|
#include "vm/compiler/backend/locations.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/stack_frame.h"
|
|
|
|
namespace dart {
|
|
|
|
namespace compiler {
|
|
|
|
namespace ffi {
|
|
|
|
static const size_t kSizeUnknown = 0;
|
|
|
|
static const intptr_t kNumElementSizes = kFfiVoidCid - kFfiPointerCid + 1;
|
|
|
|
static const size_t element_size_table[kNumElementSizes] = {
|
|
target::kWordSize, // kFfiPointerCid
|
|
kSizeUnknown, // kFfiNativeFunctionCid
|
|
1, // kFfiInt8Cid
|
|
2, // kFfiInt16Cid
|
|
4, // kFfiInt32Cid
|
|
8, // kFfiInt64Cid
|
|
1, // kFfiUint8Cid
|
|
2, // kFfiUint16Cid
|
|
4, // kFfiUint32Cid
|
|
8, // kFfiUint64Cid
|
|
target::kWordSize, // kFfiIntPtrCid
|
|
4, // kFfiFloatCid
|
|
8, // kFfiDoubleCid
|
|
kSizeUnknown, // kFfiVoidCid
|
|
};
|
|
|
|
size_t ElementSizeInBytes(intptr_t class_id) {
|
|
ASSERT(class_id != kFfiNativeFunctionCid);
|
|
ASSERT(class_id != kFfiVoidCid);
|
|
if (!RawObject::IsFfiTypeClassId(class_id)) {
|
|
// subtype of Pointer
|
|
class_id = kFfiPointerCid;
|
|
}
|
|
intptr_t index = class_id - kFfiPointerCid;
|
|
return element_size_table[index];
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
Representation TypeRepresentation(const AbstractType& result_type) {
|
|
switch (result_type.type_class_id()) {
|
|
case kFfiFloatCid:
|
|
return kUnboxedFloat;
|
|
case kFfiDoubleCid:
|
|
return kUnboxedDouble;
|
|
case kFfiInt8Cid:
|
|
case kFfiInt16Cid:
|
|
case kFfiInt32Cid:
|
|
return kUnboxedInt32;
|
|
case kFfiUint8Cid:
|
|
case kFfiUint16Cid:
|
|
case kFfiUint32Cid:
|
|
return kUnboxedUint32;
|
|
case kFfiInt64Cid:
|
|
case kFfiUint64Cid:
|
|
return kUnboxedInt64;
|
|
case kFfiIntPtrCid:
|
|
case kFfiPointerCid:
|
|
default: // Subtypes of Pointer.
|
|
return kUnboxedFfiIntPtr;
|
|
}
|
|
}
|
|
|
|
SmallRepresentation TypeSmallRepresentation(const AbstractType& ffi_type) {
|
|
switch (ffi_type.type_class_id()) {
|
|
case kFfiInt8Cid:
|
|
return kSmallUnboxedInt8;
|
|
case kFfiInt16Cid:
|
|
return kSmallUnboxedInt16;
|
|
case kFfiUint8Cid:
|
|
return kSmallUnboxedUint8;
|
|
case kFfiUint16Cid:
|
|
return kSmallUnboxedUint16;
|
|
default:
|
|
return kNoSmallRepresentation;
|
|
}
|
|
}
|
|
|
|
bool NativeTypeIsVoid(const AbstractType& result_type) {
|
|
return result_type.type_class_id() == kFfiVoidCid;
|
|
}
|
|
|
|
bool NativeTypeIsPointer(const AbstractType& result_type) {
|
|
switch (result_type.type_class_id()) {
|
|
case kFfiVoidCid:
|
|
case kFfiFloatCid:
|
|
case kFfiDoubleCid:
|
|
case kFfiInt8Cid:
|
|
case kFfiInt16Cid:
|
|
case kFfiInt32Cid:
|
|
case kFfiUint8Cid:
|
|
case kFfiUint16Cid:
|
|
case kFfiUint32Cid:
|
|
case kFfiInt64Cid:
|
|
case kFfiUint64Cid:
|
|
case kFfiIntPtrCid:
|
|
return false;
|
|
case kFfiPointerCid:
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Converts a Ffi [signature] to a list of Representations.
|
|
// Note that this ignores first argument (receiver) which is dynamic.
|
|
template <class CallingConventions>
|
|
ZoneGrowableArray<Representation>* ArgumentRepresentationsBase(
|
|
const Function& signature) {
|
|
intptr_t num_arguments = signature.num_fixed_parameters() - 1;
|
|
auto result = new ZoneGrowableArray<Representation>(num_arguments);
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
AbstractType& arg_type =
|
|
AbstractType::Handle(signature.ParameterTypeAt(i + 1));
|
|
Representation rep = TypeRepresentation(arg_type);
|
|
// In non simulator mode host::CallingConventions == CallingConventions.
|
|
// In simulator mode convert arguments to host representation.
|
|
if (rep == kUnboxedFloat && CallingConventions::kAbiSoftFP) {
|
|
rep = kUnboxedInt32;
|
|
} else if (rep == kUnboxedDouble && CallingConventions::kAbiSoftFP) {
|
|
rep = kUnboxedInt64;
|
|
}
|
|
result->Add(rep);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <class CallingConventions>
|
|
Representation ResultRepresentationBase(const Function& signature) {
|
|
AbstractType& arg_type = AbstractType::Handle(signature.result_type());
|
|
Representation rep = TypeRepresentation(arg_type);
|
|
if (rep == kUnboxedFloat && CallingConventions::kAbiSoftFP) {
|
|
rep = kUnboxedInt32;
|
|
} else if (rep == kUnboxedDouble && CallingConventions::kAbiSoftFP) {
|
|
rep = kUnboxedInt64;
|
|
}
|
|
return rep;
|
|
}
|
|
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
|
|
ZoneGrowableArray<Representation>* ArgumentRepresentations(
|
|
const Function& signature) {
|
|
return ArgumentRepresentationsBase<CallingConventions>(signature);
|
|
}
|
|
|
|
Representation ResultRepresentation(const Function& signature) {
|
|
return ResultRepresentationBase<CallingConventions>(signature);
|
|
}
|
|
|
|
#endif // !defined(TARGET_ARCH_DBC)
|
|
|
|
#if defined(USING_SIMULATOR)
|
|
|
|
ZoneGrowableArray<Representation>* ArgumentHostRepresentations(
|
|
const Function& signature) {
|
|
return ArgumentRepresentationsBase<host::CallingConventions>(signature);
|
|
}
|
|
|
|
Representation ResultHostRepresentation(const Function& signature) {
|
|
return ResultRepresentationBase<host::CallingConventions>(signature);
|
|
}
|
|
|
|
#endif // defined(USING_SIMULATOR)
|
|
|
|
// Represents the state of a stack frame going into a call, between allocations
|
|
// of argument locations. Acts like a register allocator but for arguments in
|
|
// the native ABI.
|
|
template <class CallingConventions,
|
|
class Location,
|
|
class Register,
|
|
class FpuRegister>
|
|
class ArgumentAllocator : public ValueObject {
|
|
public:
|
|
Location AllocateArgument(Representation rep) {
|
|
switch (rep) {
|
|
case kUnboxedFloat:
|
|
case kUnboxedDouble: {
|
|
Location result = AllocateFpuRegister();
|
|
if (!result.IsUnallocated()) return result;
|
|
break;
|
|
}
|
|
case kUnboxedInt64:
|
|
case kUnboxedUint32:
|
|
case kUnboxedInt32: {
|
|
Location result =
|
|
rep == kUnboxedInt64 && compiler::target::kWordSize == 4
|
|
? AllocateAlignedRegisterPair()
|
|
: AllocateCpuRegister();
|
|
if (!result.IsUnallocated()) return result;
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// Argument must be spilled.
|
|
if (rep == kUnboxedInt64 && compiler::target::kWordSize == 4) {
|
|
return AllocateAlignedStackSlots(rep);
|
|
} else if (rep == kUnboxedDouble) {
|
|
// By convention, we always use DoubleStackSlot for doubles, even on
|
|
// 64-bit systems.
|
|
ASSERT(!CallingConventions::kAlignArguments);
|
|
return AllocateDoubleStackSlot();
|
|
} else {
|
|
return AllocateStackSlot();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Location AllocateStackSlot() {
|
|
return Location::StackSlot(stack_height_in_slots++,
|
|
CallingConventions::kStackPointerRegister);
|
|
}
|
|
|
|
Location AllocateDoubleStackSlot() {
|
|
const Location result = Location::DoubleStackSlot(
|
|
stack_height_in_slots, CallingConventions::kStackPointerRegister);
|
|
stack_height_in_slots += 8 / compiler::target::kWordSize;
|
|
return result;
|
|
}
|
|
|
|
// Allocates a pair of stack slots where the first stack slot is aligned to an
|
|
// 8-byte boundary, if necessary.
|
|
Location AllocateAlignedStackSlots(Representation rep) {
|
|
if (CallingConventions::kAlignArguments &&
|
|
compiler::target::kWordSize == 4) {
|
|
stack_height_in_slots += stack_height_in_slots % 2;
|
|
}
|
|
|
|
Location result;
|
|
if (rep == kUnboxedDouble) {
|
|
result = Location::DoubleStackSlot(
|
|
stack_height_in_slots, CallingConventions::kStackPointerRegister);
|
|
stack_height_in_slots += 2;
|
|
} else {
|
|
const Location low = AllocateStackSlot();
|
|
const Location high = AllocateStackSlot();
|
|
result = Location::Pair(low, high);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Location AllocateFpuRegister() {
|
|
if (fpu_regs_used == CallingConventions::kNumFpuArgRegs) {
|
|
return Location::RequiresFpuRegister();
|
|
}
|
|
|
|
const Location result = Location::FpuRegisterLocation(
|
|
CallingConventions::FpuArgumentRegisters[fpu_regs_used]);
|
|
fpu_regs_used++;
|
|
if (CallingConventions::kArgumentIntRegXorFpuReg) {
|
|
cpu_regs_used++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Location AllocateCpuRegister() {
|
|
if (cpu_regs_used == CallingConventions::kNumArgRegs) {
|
|
return Location::RequiresRegister();
|
|
}
|
|
|
|
const Location result = Location::RegisterLocation(
|
|
CallingConventions::ArgumentRegisters[cpu_regs_used]);
|
|
cpu_regs_used++;
|
|
if (CallingConventions::kArgumentIntRegXorFpuReg) {
|
|
fpu_regs_used++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Allocates a pair of registers where the first register index is even, if
|
|
// necessary.
|
|
Location AllocateAlignedRegisterPair() {
|
|
if (CallingConventions::kAlignArguments) {
|
|
cpu_regs_used += cpu_regs_used % 2;
|
|
}
|
|
if (cpu_regs_used > CallingConventions::kNumArgRegs - 2) {
|
|
return Location::Any();
|
|
}
|
|
return Location::Pair(AllocateCpuRegister(), AllocateCpuRegister());
|
|
}
|
|
|
|
intptr_t cpu_regs_used = 0;
|
|
intptr_t fpu_regs_used = 0;
|
|
intptr_t stack_height_in_slots = 0;
|
|
};
|
|
|
|
ZoneGrowableArray<Location>*
|
|
CallbackArgumentTranslator::TranslateArgumentLocations(
|
|
const ZoneGrowableArray<Location>& arg_locs) {
|
|
auto& pushed_locs = *(new ZoneGrowableArray<Location>(arg_locs.length()));
|
|
|
|
CallbackArgumentTranslator translator;
|
|
for (intptr_t i = 0, n = arg_locs.length(); i < n; i++) {
|
|
translator.AllocateArgument(arg_locs[i]);
|
|
}
|
|
for (intptr_t i = 0, n = arg_locs.length(); i < n; ++i) {
|
|
pushed_locs.Add(translator.TranslateArgument(arg_locs[i]));
|
|
}
|
|
|
|
return &pushed_locs;
|
|
}
|
|
|
|
void CallbackArgumentTranslator::AllocateArgument(Location arg) {
|
|
if (arg.IsPairLocation()) {
|
|
AllocateArgument(arg.Component(0));
|
|
AllocateArgument(arg.Component(1));
|
|
return;
|
|
}
|
|
if (arg.HasStackIndex()) return;
|
|
ASSERT(arg.IsRegister() || arg.IsFpuRegister());
|
|
if (arg.IsRegister()) {
|
|
argument_slots_required_++;
|
|
} else {
|
|
argument_slots_required_ += 8 / compiler::target::kWordSize;
|
|
}
|
|
}
|
|
|
|
Location CallbackArgumentTranslator::TranslateArgument(Location arg) {
|
|
if (arg.IsPairLocation()) {
|
|
const Location low = TranslateArgument(arg.Component(0));
|
|
const Location high = TranslateArgument(arg.Component(1));
|
|
return Location::Pair(low, high);
|
|
}
|
|
|
|
if (arg.HasStackIndex()) {
|
|
// 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).
|
|
FrameRebase rebase(
|
|
/*old_base=*/SPREG, /*new_base=*/SPREG,
|
|
/*stack_delta=*/argument_slots_required_ +
|
|
kCallbackSlotsBeforeSavedArguments);
|
|
return rebase.Rebase(arg);
|
|
}
|
|
|
|
if (arg.IsRegister()) {
|
|
return Location::StackSlot(argument_slots_used_++, SPREG);
|
|
}
|
|
|
|
ASSERT(arg.IsFpuRegister());
|
|
const Location result =
|
|
Location::DoubleStackSlot(argument_slots_used_, SPREG);
|
|
argument_slots_used_ += 8 / compiler::target::kWordSize;
|
|
return result;
|
|
}
|
|
|
|
// Takes a list of argument representations, and converts it to a list of
|
|
// argument locations based on calling convention.
|
|
template <class CallingConventions,
|
|
class Location,
|
|
class Register,
|
|
class FpuRegister>
|
|
ZoneGrowableArray<Location>* ArgumentLocationsBase(
|
|
const ZoneGrowableArray<Representation>& arg_reps) {
|
|
intptr_t num_arguments = arg_reps.length();
|
|
auto result = new ZoneGrowableArray<Location>(num_arguments);
|
|
|
|
// Loop through all arguments and assign a register or a stack location.
|
|
ArgumentAllocator<CallingConventions, Location, Register, FpuRegister>
|
|
frame_state;
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
Representation rep = arg_reps[i];
|
|
result->Add(frame_state.AllocateArgument(rep));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ZoneGrowableArray<Location>* ArgumentLocations(
|
|
const ZoneGrowableArray<Representation>& arg_reps) {
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
return ArgumentLocationsBase<dart::CallingConventions, Location,
|
|
dart::Register, dart::FpuRegister>(arg_reps);
|
|
#else
|
|
intptr_t next_free_register = compiler::ffi::kFirstArgumentRegister;
|
|
intptr_t num_arguments = arg_reps.length();
|
|
auto result = new ZoneGrowableArray<Location>(num_arguments);
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
// TODO(dacoharkes): In 32 bits, use pair locations.
|
|
result->Add(Location::RegisterLocation(next_free_register));
|
|
next_free_register++;
|
|
}
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
#if defined(TARGET_ARCH_DBC)
|
|
ZoneGrowableArray<HostLocation>* HostArgumentLocations(
|
|
const ZoneGrowableArray<Representation>& arg_reps) {
|
|
return ArgumentLocationsBase<dart::host::CallingConventions, HostLocation,
|
|
dart::host::Register, dart::host::FpuRegister>(
|
|
arg_reps);
|
|
}
|
|
#endif
|
|
|
|
Location ResultLocation(Representation result_rep) {
|
|
#ifndef TARGET_ARCH_DBC
|
|
switch (result_rep) {
|
|
case kUnboxedFloat:
|
|
case kUnboxedDouble:
|
|
#if defined(TARGET_ARCH_IA32)
|
|
// The result is returned in ST0, but we don't allocate ST registers, so
|
|
// the FFI trampoline will move it to XMM0.
|
|
return Location::FpuRegisterLocation(XMM0);
|
|
#else
|
|
return Location::FpuRegisterLocation(CallingConventions::kReturnFpuReg);
|
|
#endif
|
|
case kUnboxedInt32:
|
|
case kUnboxedUint32:
|
|
return Location::RegisterLocation(CallingConventions::kReturnReg);
|
|
case kUnboxedInt64:
|
|
if (compiler::target::kWordSize == 4) {
|
|
return Location::Pair(
|
|
Location::RegisterLocation(CallingConventions::kReturnReg),
|
|
Location::RegisterLocation(CallingConventions::kSecondReturnReg));
|
|
} else {
|
|
return Location::RegisterLocation(CallingConventions::kReturnReg);
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
#else
|
|
// TODO(dacoharkes): Support 64 bit result values on 32 bit DBC.
|
|
return Location::RegisterLocation(0);
|
|
#endif
|
|
}
|
|
|
|
// Accounts for alignment, where some stack slots are used as padding.
|
|
template <class Location>
|
|
intptr_t TemplateNumStackSlots(const ZoneGrowableArray<Location>& locations) {
|
|
intptr_t num_arguments = locations.length();
|
|
intptr_t max_height_in_slots = 0;
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
intptr_t height = 0;
|
|
if (locations.At(i).IsStackSlot()) {
|
|
height = locations.At(i).stack_index() + 1;
|
|
} else if (locations.At(i).IsDoubleStackSlot()) {
|
|
height = locations.At(i).stack_index() + 8 / compiler::target::kWordSize;
|
|
} else if (locations.At(i).IsPairLocation()) {
|
|
const Location first = locations.At(i).AsPairLocation()->At(0);
|
|
const Location second = locations.At(i).AsPairLocation()->At(1);
|
|
height = std::max(first.IsStackSlot() ? first.stack_index() + 1 : 0,
|
|
second.IsStackSlot() ? second.stack_index() + 1 : 0);
|
|
}
|
|
max_height_in_slots = std::max(height, max_height_in_slots);
|
|
}
|
|
return max_height_in_slots;
|
|
}
|
|
|
|
intptr_t NumStackSlots(const ZoneGrowableArray<Location>& locations) {
|
|
return TemplateNumStackSlots(locations);
|
|
}
|
|
|
|
#if defined(TARGET_ARCH_DBC)
|
|
|
|
static RawTypedData* typed_data_new_uintptr(intptr_t length) {
|
|
#if defined(ARCH_IS_32_BIT)
|
|
return TypedData::New(kTypedDataUint32ArrayCid, length);
|
|
#else
|
|
return TypedData::New(kTypedDataUint64ArrayCid, length);
|
|
#endif
|
|
}
|
|
|
|
static void typed_data_set_uintptr(const TypedData& typed_data,
|
|
intptr_t index,
|
|
uintptr_t value) {
|
|
#if defined(ARCH_IS_32_BIT)
|
|
typed_data.SetUint32(target::kWordSize * index, value);
|
|
#else
|
|
typed_data.SetUint64(target::kWordSize * index, value);
|
|
#endif
|
|
}
|
|
|
|
static uintptr_t typed_data_get_uintptr(const TypedData& typed_data,
|
|
intptr_t index) {
|
|
#if defined(ARCH_IS_32_BIT)
|
|
return typed_data.GetUint32(target::kWordSize * index);
|
|
#else
|
|
return typed_data.GetUint64(target::kWordSize * index);
|
|
#endif
|
|
}
|
|
|
|
// Number of host stack slots used in 'locations'.
|
|
static intptr_t HostNumStackSlots(
|
|
const ZoneGrowableArray<HostLocation>& locations) {
|
|
return TemplateNumStackSlots(locations);
|
|
}
|
|
|
|
RawTypedData* FfiSignatureDescriptor::New(
|
|
const ZoneGrowableArray<HostLocation>& arg_host_locations,
|
|
const Representation result_representation) {
|
|
const uintptr_t num_arguments = arg_host_locations.length();
|
|
const uintptr_t num_stack_slots = HostNumStackSlots(arg_host_locations);
|
|
|
|
const TypedData& result = TypedData::Handle(
|
|
typed_data_new_uintptr(kOffsetArgumentLocations + num_arguments));
|
|
|
|
typed_data_set_uintptr(result, kOffsetNumArguments, num_arguments);
|
|
typed_data_set_uintptr(result, kOffsetNumStackSlots, num_stack_slots);
|
|
typed_data_set_uintptr(result, kOffsetResultRepresentation,
|
|
result_representation);
|
|
|
|
for (uintptr_t i = 0; i < num_arguments; i++) {
|
|
typed_data_set_uintptr(result, kOffsetArgumentLocations + i,
|
|
arg_host_locations.At(i).write());
|
|
}
|
|
|
|
return result.raw();
|
|
}
|
|
|
|
intptr_t FfiSignatureDescriptor::length() const {
|
|
return typed_data_get_uintptr(typed_data_, kOffsetNumArguments);
|
|
}
|
|
|
|
intptr_t FfiSignatureDescriptor::num_stack_slots() const {
|
|
return typed_data_get_uintptr(typed_data_, kOffsetNumStackSlots);
|
|
}
|
|
|
|
HostLocation FfiSignatureDescriptor::LocationAt(intptr_t index) const {
|
|
return HostLocation::read(
|
|
typed_data_get_uintptr(typed_data_, kOffsetArgumentLocations + index));
|
|
}
|
|
|
|
Representation FfiSignatureDescriptor::ResultRepresentation() const {
|
|
uintptr_t result_int =
|
|
typed_data_get_uintptr(typed_data_, kOffsetResultRepresentation);
|
|
ASSERT(result_int < kNumRepresentations);
|
|
return static_cast<Representation>(result_int);
|
|
}
|
|
|
|
#endif // defined(TARGET_ARCH_DBC)
|
|
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
} // namespace ffi
|
|
|
|
} // namespace compiler
|
|
|
|
} // namespace dart
|