// 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 #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 ZoneGrowableArray* ArgumentRepresentationsBase( const Function& signature) { intptr_t num_arguments = signature.num_fixed_parameters() - 1; auto result = new ZoneGrowableArray(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 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* ArgumentRepresentations( const Function& signature) { return ArgumentRepresentationsBase(signature); } Representation ResultRepresentation(const Function& signature) { return ResultRepresentationBase(signature); } #endif // !defined(TARGET_ARCH_DBC) #if defined(USING_SIMULATOR) ZoneGrowableArray* ArgumentHostRepresentations( const Function& signature) { return ArgumentRepresentationsBase(signature); } Representation ResultHostRepresentation(const Function& signature) { return ResultRepresentationBase(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 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* CallbackArgumentTranslator::TranslateArgumentLocations( const ZoneGrowableArray& arg_locs) { auto& pushed_locs = *(new ZoneGrowableArray(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 ZoneGrowableArray* ArgumentLocationsBase( const ZoneGrowableArray& arg_reps) { intptr_t num_arguments = arg_reps.length(); auto result = new ZoneGrowableArray(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; } ZoneGrowableArray* ArgumentLocations( const ZoneGrowableArray& arg_reps) { #if !defined(TARGET_ARCH_DBC) return ArgumentLocationsBase(arg_reps); #else intptr_t next_free_register = compiler::ffi::kFirstArgumentRegister; intptr_t num_arguments = arg_reps.length(); auto result = new ZoneGrowableArray(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* HostArgumentLocations( const ZoneGrowableArray& arg_reps) { return ArgumentLocationsBase( 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 intptr_t TemplateNumStackSlots(const ZoneGrowableArray& 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& 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& locations) { return TemplateNumStackSlots(locations); } RawTypedData* FfiSignatureDescriptor::New( const ZoneGrowableArray& 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(result_int); } #endif // defined(TARGET_ARCH_DBC) #endif // !defined(DART_PRECOMPILED_RUNTIME) } // namespace ffi } // namespace compiler } // namespace dart