blob: 17aed54f68cb5ab680487e3c35072a889d223681 [file] [log] [blame]
// 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/native_calling_convention.h"
#include "vm/compiler/ffi/frame_rebase.h"
#include "vm/log.h"
#include "vm/stack_frame.h"
#include "vm/symbols.h"
namespace dart {
namespace compiler {
namespace ffi {
#if !defined(DART_PRECOMPILED_RUNTIME)
Representation TypeRepresentation(classid_t class_id) {
switch (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:
return kUnboxedIntPtr;
case kFfiPointerCid:
case kFfiVoidCid:
return kUnboxedFfiIntPtr;
default:
UNREACHABLE();
}
}
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) {
return result_type.type_class_id() == kFfiPointerCid;
}
// Converts a Ffi [signature] to a list of Representations.
// Note that this ignores first argument (receiver) which is dynamic.
ZoneGrowableArray<Representation>* ArgumentRepresentations(
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.type_class_id());
// 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;
}
Representation ResultRepresentation(const Function& signature) {
AbstractType& arg_type = AbstractType::Handle(signature.result_type());
Representation rep = TypeRepresentation(arg_type.type_class_id());
if (rep == kUnboxedFloat && CallingConventions::kAbiSoftFP) {
rep = kUnboxedInt32;
} else if (rep == kUnboxedDouble && CallingConventions::kAbiSoftFP) {
rep = kUnboxedInt64;
}
return rep;
}
// 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.
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 && target::kWordSize == 4
? AllocateAlignedRegisterPair()
: AllocateCpuRegister();
if (!result.IsUnallocated()) return result;
break;
}
default:
UNREACHABLE();
}
// Argument must be spilled.
if (rep == kUnboxedInt64 && 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 / 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 && 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;
};
// Takes a list of argument representations, and converts it to a list of
// argument locations based on calling convention.
ZoneGrowableArray<Location>* ArgumentLocations(
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 frame_state;
for (intptr_t i = 0; i < num_arguments; i++) {
Representation rep = arg_reps[i];
result->Add(frame_state.AllocateArgument(rep));
}
return result;
}
Location ResultLocation(Representation result_rep) {
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 (target::kWordSize == 4) {
return Location::Pair(
Location::RegisterLocation(CallingConventions::kReturnReg),
Location::RegisterLocation(CallingConventions::kSecondReturnReg));
} else {
return Location::RegisterLocation(CallingConventions::kReturnReg);
}
default:
UNREACHABLE();
}
}
// Accounts for alignment, where some stack slots are used as padding.
intptr_t NumStackSlots(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 / 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 =
Utils::Maximum(first.IsStackSlot() ? first.stack_index() + 1 : 0,
second.IsStackSlot() ? second.stack_index() + 1 : 0);
}
max_height_in_slots = Utils::Maximum(height, max_height_in_slots);
}
return max_height_in_slots;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
} // namespace ffi
} // namespace compiler
} // namespace dart