// 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/native_location.h" #include "vm/compiler/ffi/native_type.h" #include "vm/zone_text_buffer.h" #if !defined(FFI_UNIT_TESTS) #include "vm/cpu.h" #endif namespace dart { namespace compiler { namespace ffi { const intptr_t kNoFpuRegister = -1; #if !defined(FFI_UNIT_TESTS) // In Soft FP, floats and doubles get passed in integer registers. static bool SoftFpAbi() { #if defined(TARGET_ARCH_ARM) return !TargetCPUFeatures::hardfp_supported(); #else return false; #endif } #else // !defined(FFI_UNIT_TESTS) static bool SoftFpAbi() { #if defined(TARGET_ARCH_ARM) && defined(TARGET_OS_ANDROID) return true; #else return false; #endif } #endif // !defined(FFI_UNIT_TESTS) // In Soft FP, floats are treated as 4 byte ints, and doubles as 8 byte ints. static const NativeType& ConvertIfSoftFp(Zone* zone, const NativeType& rep) { if (SoftFpAbi() && rep.IsFloat()) { ASSERT(rep.IsFloat()); if (rep.SizeInBytes() == 4) { return *new (zone) NativePrimitiveType(kInt32); } if (rep.SizeInBytes() == 8) { return *new (zone) NativePrimitiveType(kInt64); } } return rep; } // The native dual of `kUnboxedFfiIntPtr`. // // It has the same signedness as `kUnboxedFfiIntPtr` to avoid sign conversions // when converting between both. const PrimitiveType kFfiIntPtr = compiler::target::kWordSize == 8 ? kInt64 : kUint32; // Represents the state of a stack frame going into a call, between allocations // of argument locations. class ArgumentAllocator : public ValueObject { public: explicit ArgumentAllocator(Zone* zone) : zone_(zone) {} const NativeLocation& AllocateArgument(const NativeType& payload_type) { const auto& payload_type_converted = ConvertIfSoftFp(zone_, payload_type); if (payload_type_converted.IsFloat()) { return AllocateFloat(payload_type); } if (payload_type_converted.IsInt()) { return AllocateInt(payload_type); } // Compounds are laid out differently per ABI, so they are implemented // per ABI. // // Compounds always have a PointerToMemory, Stack, or Multiple location, // even if the parts of a compound fit in 1 cpu or fpu register it will // be nested in a MultipleNativeLocations. const NativeCompoundType& compound_type = payload_type.AsCompound(); return AllocateCompound(compound_type); } private: const NativeLocation& AllocateFloat(const NativeType& payload_type) { const auto kind = FpuRegKind(payload_type); const intptr_t reg_index = FirstFreeFpuRegisterIndex(kind); if (reg_index != kNoFpuRegister) { AllocateFpuRegisterAtIndex(kind, reg_index); if (CallingConventions::kArgumentIntRegXorFpuReg) { cpu_regs_used++; } return *new (zone_) NativeFpuRegistersLocation(payload_type, payload_type, kind, reg_index); } BlockAllFpuRegisters(); if (CallingConventions::kArgumentIntRegXorFpuReg) { ASSERT(cpu_regs_used == CallingConventions::kNumArgRegs); } return AllocateStack(payload_type); } const NativeLocation& AllocateInt(const NativeType& payload_type) { const auto& payload_type_converted = ConvertIfSoftFp(zone_, payload_type); // Some calling conventions require the callee to make the lowest 32 bits // in registers non-garbage. const auto& container_type = CallingConventions::kArgumentRegisterExtension == kExtendedTo4 ? payload_type_converted.WidenTo4Bytes(zone_) : payload_type_converted; if (target::kWordSize == 4 && payload_type.SizeInBytes() == 8) { if (CallingConventions::kArgumentRegisterAlignment == kAlignedToWordSizeBut8AlignedTo8) { cpu_regs_used += cpu_regs_used % 2; } if (cpu_regs_used + 2 <= CallingConventions::kNumArgRegs) { const Register register_1 = AllocateCpuRegister(); const Register register_2 = AllocateCpuRegister(); return *new (zone_) NativeRegistersLocation( zone_, payload_type, container_type, register_1, register_2); } } else { ASSERT(payload_type.SizeInBytes() <= target::kWordSize); if (cpu_regs_used + 1 <= CallingConventions::kNumArgRegs) { return *new (zone_) NativeRegistersLocation( zone_, payload_type, container_type, AllocateCpuRegister()); } } return AllocateStack(payload_type); } #if defined(TARGET_ARCH_X64) && !defined(TARGET_OS_WINDOWS) // If fits in two fpu and/or cpu registers, transfer in those. Otherwise, // transfer on stack. const NativeLocation& AllocateCompound( const NativeCompoundType& payload_type) { const intptr_t size = payload_type.SizeInBytes(); if (size <= 16 && size > 0 && !payload_type.ContainsUnalignedMembers()) { intptr_t required_regs = payload_type.NumberOfWordSizeChunksNotOnlyFloat(); intptr_t required_xmm_regs = payload_type.NumberOfWordSizeChunksOnlyFloat(); const bool regs_available = cpu_regs_used + required_regs <= CallingConventions::kNumArgRegs; const bool fpu_regs_available = FirstFreeFpuRegisterIndex(kQuadFpuReg) != kNoFpuRegister && FirstFreeFpuRegisterIndex(kQuadFpuReg) + required_xmm_regs <= CallingConventions::kNumFpuArgRegs; if (regs_available && fpu_regs_available) { // Transfer in registers. NativeLocations& multiple_locations = *new (zone_) NativeLocations( zone_, required_regs + required_xmm_regs); for (intptr_t offset = 0; offset < size; offset += compiler::target::kWordSize) { if (payload_type.ContainsOnlyFloats(Range::StartAndEnd( offset, Utils::Minimum(size, offset + 8)))) { const intptr_t reg_index = FirstFreeFpuRegisterIndex(kQuadFpuReg); AllocateFpuRegisterAtIndex(kQuadFpuReg, reg_index); const auto& type = *new (zone_) NativePrimitiveType(kDouble); multiple_locations.Add(new (zone_) NativeFpuRegistersLocation( type, type, kQuadFpuReg, reg_index)); } else { const auto& type = *new (zone_) NativePrimitiveType(kInt64); multiple_locations.Add(new (zone_) NativeRegistersLocation( zone_, type, type, AllocateCpuRegister())); } } return *new (zone_) MultipleNativeLocations(payload_type, multiple_locations); } } return AllocateStack(payload_type); } #endif // defined(TARGET_ARCH_X64) && !defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_X64) && defined(TARGET_OS_WINDOWS) // If struct fits in a single register and size is a power of two, then // use a single register and sign extend. // Otherwise, pass a pointer to a copy. const NativeLocation& AllocateCompound( const NativeCompoundType& payload_type) { const NativeCompoundType& compound_type = payload_type.AsCompound(); const intptr_t size = compound_type.SizeInBytes(); if (size <= 8 && Utils::IsPowerOfTwo(size)) { if (cpu_regs_used < CallingConventions::kNumArgRegs) { NativeLocations& multiple_locations = *new (zone_) NativeLocations(zone_, 1); const auto& type = *new (zone_) NativePrimitiveType( PrimitiveTypeFromSizeInBytes(size)); multiple_locations.Add(new (zone_) NativeRegistersLocation( zone_, type, type, AllocateCpuRegister())); return *new (zone_) MultipleNativeLocations(compound_type, multiple_locations); } } else if (size > 0) { // Pointer in register if available, else pointer on stack. const auto& pointer_type = *new (zone_) NativePrimitiveType(kFfiIntPtr); const auto& pointer_location = AllocateArgument(pointer_type); return *new (zone_) PointerToMemoryLocation(pointer_location, compound_type); } return AllocateStack(payload_type); } #endif // defined(TARGET_ARCH_X64) && defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_IA32) const NativeLocation& AllocateCompound( const NativeCompoundType& payload_type) { return AllocateStack(payload_type); } #endif // defined(TARGET_ARCH_IA32) #if defined(TARGET_ARCH_ARM) // Transfer homogenuous floats in FPU registers, and allocate the rest // in 4 or 8 size chunks in registers and stack. const NativeLocation& AllocateCompound( const NativeCompoundType& payload_type) { const auto& compound_type = payload_type.AsCompound(); if (compound_type.ContainsHomogenuousFloats() && !SoftFpAbi() && compound_type.NumPrimitiveMembersRecursive() <= 4) { const auto& elem_type = compound_type.FirstPrimitiveMember(); const intptr_t size = compound_type.SizeInBytes(); const intptr_t elem_size = elem_type.SizeInBytes(); const auto reg_kind = FpuRegisterKindFromSize(elem_size); ASSERT(size % elem_size == 0); const intptr_t num_registers = size / elem_size; const intptr_t first_reg = FirstFreeFpuRegisterIndex(reg_kind, num_registers); if (first_reg != kNoFpuRegister) { AllocateFpuRegisterAtIndex(reg_kind, first_reg, num_registers); NativeLocations& multiple_locations = *new (zone_) NativeLocations(zone_, num_registers); for (int i = 0; i < num_registers; i++) { const intptr_t reg_index = first_reg + i; multiple_locations.Add(new (zone_) NativeFpuRegistersLocation( elem_type, elem_type, reg_kind, reg_index)); } return *new (zone_) MultipleNativeLocations(compound_type, multiple_locations); } else { BlockAllFpuRegisters(); return AllocateStack(payload_type); } } else { const intptr_t chunck_size = payload_type.AlignmentInBytesStack(); ASSERT(chunck_size == 4 || chunck_size == 8); const intptr_t size_rounded = Utils::RoundUp(payload_type.SizeInBytes(), chunck_size); const intptr_t num_chuncks = size_rounded / chunck_size; const auto& chuck_type = *new (zone_) NativePrimitiveType(chunck_size == 4 ? kInt32 : kInt64); NativeLocations& multiple_locations = *new (zone_) NativeLocations(zone_, num_chuncks); for (int i = 0; i < num_chuncks; i++) { const auto& allocated_chunk = &AllocateArgument(chuck_type); // The last chunk should not be 8 bytes, if the struct only has 4 // remaining bytes to be allocated. if (i == num_chuncks - 1 && chunck_size == 8 && Utils::RoundUp(payload_type.SizeInBytes(), 4) % 8 == 4) { const auto& small_chuck_type = *new (zone_) NativePrimitiveType( chunck_size == 4 ? kInt32 : kInt64); multiple_locations.Add(&allocated_chunk->WithOtherNativeType( zone_, small_chuck_type, small_chuck_type)); } else { multiple_locations.Add(allocated_chunk); } } return *new (zone_) MultipleNativeLocations(compound_type, multiple_locations); } } #endif // defined(TARGET_ARCH_ARM) #if defined(TARGET_ARCH_ARM64) // Slightly different from Arm32. FPU registers don't alias the same way, // structs up to 16 bytes block remaining registers if they do not fit in // registers, and larger structs go on stack always. const NativeLocation& AllocateCompound( const NativeCompoundType& payload_type) { const auto& compound_type = payload_type.AsCompound(); const intptr_t size = compound_type.SizeInBytes(); if (compound_type.ContainsHomogenuousFloats() && compound_type.NumPrimitiveMembersRecursive() <= 4) { const auto& elem_type = compound_type.FirstPrimitiveMember(); const intptr_t elem_size = elem_type.SizeInBytes(); const auto reg_kind = kQuadFpuReg; ASSERT(size % elem_size == 0); const intptr_t num_registers = size / elem_size; const intptr_t first_reg = FirstFreeFpuRegisterIndex(reg_kind, num_registers); if (first_reg != kNoFpuRegister) { AllocateFpuRegisterAtIndex(reg_kind, first_reg, num_registers); NativeLocations& multiple_locations = *new (zone_) NativeLocations(zone_, num_registers); for (int i = 0; i < num_registers; i++) { const intptr_t reg_index = first_reg + i; multiple_locations.Add(new (zone_) NativeFpuRegistersLocation( elem_type, elem_type, reg_kind, reg_index)); } return *new (zone_) MultipleNativeLocations(compound_type, multiple_locations); } BlockAllFpuRegisters(); return AllocateStack(payload_type); } if (size <= 16) { const intptr_t required_regs = size / 8; const bool regs_available = cpu_regs_used + required_regs <= CallingConventions::kNumArgRegs; if (regs_available) { const intptr_t size_rounded = Utils::RoundUp(payload_type.SizeInBytes(), 8); const intptr_t num_chuncks = size_rounded / 8; const auto& chuck_type = *new (zone_) NativePrimitiveType(kInt64); NativeLocations& multiple_locations = *new (zone_) NativeLocations(zone_, num_chuncks); for (int i = 0; i < num_chuncks; i++) { const auto& allocated_chunk = &AllocateArgument(chuck_type); multiple_locations.Add(allocated_chunk); } return *new (zone_) MultipleNativeLocations(compound_type, multiple_locations); } else { // Block all CPU registers. cpu_regs_used = CallingConventions::kNumArgRegs; return AllocateStack(payload_type); } } const auto& pointer_location = AllocateArgument(*new (zone_) NativePrimitiveType(kInt64)); return *new (zone_) PointerToMemoryLocation(pointer_location, compound_type); } #endif // defined(TARGET_ARCH_ARM64) static FpuRegisterKind FpuRegKind(const NativeType& payload_type) { #if defined(TARGET_ARCH_ARM) return FpuRegisterKindFromSize(payload_type.SizeInBytes()); #else return kQuadFpuReg; #endif } Register AllocateCpuRegister() { RELEASE_ASSERT(cpu_regs_used >= 0); // Avoids -Werror=array-bounds in GCC. ASSERT(cpu_regs_used < CallingConventions::kNumArgRegs); const auto result = CallingConventions::ArgumentRegisters[cpu_regs_used]; if (CallingConventions::kArgumentIntRegXorFpuReg) { AllocateFpuRegisterAtIndex(kQuadFpuReg, cpu_regs_used); } cpu_regs_used++; return result; } const NativeLocation& AllocateStack(const NativeType& payload_type) { align_stack(payload_type.AlignmentInBytesStack()); const intptr_t size = payload_type.SizeInBytes(); // If the stack arguments are not packed, the 32 lowest bits should not // contain garbage. const auto& container_type = CallingConventions::kArgumentStackExtension == kExtendedTo4 ? payload_type.WidenTo4Bytes(zone_) : payload_type; const auto& result = *new (zone_) NativeStackLocation( payload_type, container_type, CallingConventions::kStackPointerRegister, stack_height_in_bytes); stack_height_in_bytes += size; return result; } void align_stack(intptr_t alignment) { stack_height_in_bytes = Utils::RoundUp(stack_height_in_bytes, alignment); } int NumFpuRegisters(FpuRegisterKind kind) { #if defined(TARGET_ARCH_ARM) if (SoftFpAbi()) return 0; if (kind == kSingleFpuReg) return CallingConventions::kNumSFpuArgRegs; if (kind == kDoubleFpuReg) return CallingConventions::kNumDFpuArgRegs; #endif // defined(TARGET_ARCH_ARM) if (kind == kQuadFpuReg) return CallingConventions::kNumFpuArgRegs; UNREACHABLE(); } // If no register is free, returns -1. int FirstFreeFpuRegisterIndex(FpuRegisterKind kind, int amount = 1) { const intptr_t size = SizeFromFpuRegisterKind(kind) / 4; ASSERT(size == 1 || size == 2 || size == 4); if (fpu_reg_parts_used == -1) return kNoFpuRegister; const intptr_t mask = (1 << (size * amount)) - 1; intptr_t index = 0; while (index + amount <= NumFpuRegisters(kind)) { const intptr_t mask_shifted = mask << (index * size); if ((fpu_reg_parts_used & mask_shifted) == 0) { return index; } index++; } return kNoFpuRegister; } void AllocateFpuRegisterAtIndex(FpuRegisterKind kind, int index, int amount = 1) { const intptr_t size = SizeFromFpuRegisterKind(kind) / 4; ASSERT(size == 1 || size == 2 || size == 4); const intptr_t mask = (1 << size * amount) - 1; const intptr_t mask_shifted = (mask << (index * size)); ASSERT((mask_shifted & fpu_reg_parts_used) == 0); fpu_reg_parts_used |= mask_shifted; } // > The back-filling continues only so long as no VFP CPRC has been // > allocated to a slot on the stack. // Procedure Call Standard for the Arm Architecture, Release 2019Q1.1 // Chapter 7.1 page 28. https://developer.arm.com/docs/ihi0042/h // // Irrelevant on Android and iOS, as those are both SoftFP. // > For floating-point arguments, the Base Standard variant of the // > Procedure Call Standard is used. In this variant, floating-point // > (and vector) arguments are passed in general purpose registers // > (GPRs) instead of in VFP registers) // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv7FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009022-SW1 void BlockAllFpuRegisters() { // Set all bits to 1. fpu_reg_parts_used = -1; } intptr_t cpu_regs_used = 0; // Every bit denotes 32 bits of FPU registers. intptr_t fpu_reg_parts_used = 0; intptr_t stack_height_in_bytes = 0; Zone* zone_; }; // Location for the arguments of a C signature function. static NativeLocations& ArgumentLocations( Zone* zone, const ZoneGrowableArray& arg_reps, const NativeLocation& return_location) { intptr_t num_arguments = arg_reps.length(); auto& result = *new (zone) NativeLocations(zone, num_arguments); // Loop through all arguments and assign a register or a stack location. // Allocate result pointer for composite returns first. ArgumentAllocator frame_state(zone); #if !defined(TARGET_ARCH_ARM64) // Arm64 allocates the pointer in R8, which is not an argument location. if (return_location.IsPointerToMemory()) { const auto& pointer_location = return_location.AsPointerToMemory().pointer_location(); const auto& pointer_location_allocated = frame_state.AllocateArgument(pointer_location.payload_type()); ASSERT(pointer_location.Equals(pointer_location_allocated)); } #endif for (intptr_t i = 0; i < num_arguments; i++) { const NativeType& rep = *arg_reps[i]; result.Add(&frame_state.AllocateArgument(rep)); } return result; } #if !defined(TARGET_ARCH_IA32) static const NativeLocation& PointerToMemoryResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const auto& pointer_type = *new (zone) NativePrimitiveType(kFfiIntPtr); const auto& pointer_location = *new (zone) NativeRegistersLocation( zone, pointer_type, pointer_type, CallingConventions::kPointerToReturnStructRegisterCall); const auto& pointer_return_location = *new (zone) NativeRegistersLocation( zone, pointer_type, pointer_type, CallingConventions::kPointerToReturnStructRegisterReturn); return *new (zone) PointerToMemoryLocation( pointer_location, pointer_return_location, payload_type); } #endif // !defined(TARGET_ARCH_IA32) #if defined(TARGET_ARCH_IA32) // ia32 Passes pointers to result locations on the stack. static const NativeLocation& PointerToMemoryResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const auto& pointer_type = *new (zone) NativePrimitiveType(kFfiIntPtr); const auto& pointer_location = *new (zone) NativeStackLocation( pointer_type, pointer_type, CallingConventions::kStackPointerRegister, 0); const auto& pointer_return_location = *new (zone) NativeRegistersLocation( zone, pointer_type, pointer_type, CallingConventions::kPointerToReturnStructRegisterReturn); return *new (zone) PointerToMemoryLocation( pointer_location, pointer_return_location, payload_type); } #endif // defined(TARGET_ARCH_IA32) #if defined(TARGET_ARCH_X64) && !defined(TARGET_OS_WINDOWS) static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const intptr_t size = payload_type.SizeInBytes(); if (size <= 16 && size > 0 && !payload_type.ContainsUnalignedMembers()) { // Allocate the same as argument, but use return registers instead of // argument registers. NativeLocations& multiple_locations = *new (zone) NativeLocations(zone, size > 8 ? 2 : 1); intptr_t used_regs = 0; intptr_t used_xmm_regs = 0; const auto& double_type = *new (zone) NativePrimitiveType(kDouble); const auto& int64_type = *new (zone) NativePrimitiveType(kInt64); const bool first_half_in_xmm = payload_type.ContainsOnlyFloats( Range::StartAndEnd(0, Utils::Minimum(size, 8))); if (first_half_in_xmm) { multiple_locations.Add(new (zone) NativeFpuRegistersLocation( double_type, double_type, kQuadFpuReg, CallingConventions::kReturnFpuReg)); used_xmm_regs++; } else { multiple_locations.Add(new (zone) NativeRegistersLocation( zone, int64_type, int64_type, CallingConventions::kReturnReg)); used_regs++; } if (size > 8) { const bool second_half_in_xmm = payload_type.ContainsOnlyFloats( Range::StartAndEnd(8, Utils::Minimum(size, 16))); if (second_half_in_xmm) { const FpuRegister reg = used_xmm_regs == 0 ? CallingConventions::kReturnFpuReg : CallingConventions::kSecondReturnFpuReg; multiple_locations.Add(new (zone) NativeFpuRegistersLocation( double_type, double_type, kQuadFpuReg, reg)); used_xmm_regs++; } else { const Register reg = used_regs == 0 ? CallingConventions::kReturnReg : CallingConventions::kSecondReturnReg; multiple_locations.Add(new (zone) NativeRegistersLocation( zone, int64_type, int64_type, reg)); used_regs++; } } return *new (zone) MultipleNativeLocations(payload_type, multiple_locations); } return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_X64) && !defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_X64) && defined(TARGET_OS_WINDOWS) // If struct fits in a single register do that, and sign extend. // Otherwise, pass a pointer to memory. static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const intptr_t size = payload_type.SizeInBytes(); if (size <= 8 && size > 0 && Utils::IsPowerOfTwo(size)) { NativeLocations& multiple_locations = *new (zone) NativeLocations(zone, 1); const auto& type = *new (zone) NativePrimitiveType(PrimitiveTypeFromSizeInBytes(size)); multiple_locations.Add(new (zone) NativeRegistersLocation( zone, type, type, CallingConventions::kReturnReg)); return *new (zone) MultipleNativeLocations(payload_type, multiple_locations); } return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_X64) && defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_IA32) && !defined(TARGET_OS_WINDOWS) static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_IA32) && !defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_IA32) && defined(TARGET_OS_WINDOWS) // Windows uses up to two return registers, while Linux does not. static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const intptr_t size = payload_type.SizeInBytes(); if (size <= 8 && Utils::IsPowerOfTwo(size)) { NativeLocations& multiple_locations = *new (zone) NativeLocations(zone, size > 4 ? 2 : 1); const auto& type = *new (zone) NativePrimitiveType(kUint32); multiple_locations.Add(new (zone) NativeRegistersLocation( zone, type, type, CallingConventions::kReturnReg)); if (size > 4) { multiple_locations.Add(new (zone) NativeRegistersLocation( zone, type, type, CallingConventions::kSecondReturnReg)); } return *new (zone) MultipleNativeLocations(payload_type, multiple_locations); } return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_IA32) && defined(TARGET_OS_WINDOWS) #if defined(TARGET_ARCH_ARM) // Arm passes homogenous float return values in FPU registers and small // composities in a single integer register. The rest is stored into the // location passed in by pointer. static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { const intptr_t num_members = payload_type.NumPrimitiveMembersRecursive(); if (payload_type.ContainsHomogenuousFloats() && !SoftFpAbi() && num_members <= 4) { NativeLocations& multiple_locations = *new (zone) NativeLocations(zone, num_members); for (int i = 0; i < num_members; i++) { const auto& member = payload_type.FirstPrimitiveMember(); multiple_locations.Add(new (zone) NativeFpuRegistersLocation( member, member, FpuRegisterKindFromSize(member.SizeInBytes()), i)); } return *new (zone) MultipleNativeLocations(payload_type, multiple_locations); } const intptr_t size = payload_type.SizeInBytes(); if (size <= 4) { NativeLocations& multiple_locations = *new (zone) NativeLocations(zone, 1); const auto& type = *new (zone) NativePrimitiveType(kUint32); multiple_locations.Add(new (zone) NativeRegistersLocation(zone, type, type, R0)); return *new (zone) MultipleNativeLocations(payload_type, multiple_locations); } return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_ARM) #if defined(TARGET_ARCH_ARM64) // If allocated to integer or fpu registers as argument, same for return, // otherwise a pointer to the result location is passed in. static const NativeLocation& CompoundResultLocation( Zone* zone, const NativeCompoundType& payload_type) { ArgumentAllocator frame_state(zone); const auto& location_as_argument = frame_state.AllocateArgument(payload_type); if (!location_as_argument.IsStack() && !location_as_argument.IsPointerToMemory()) { return location_as_argument; } return PointerToMemoryResultLocation(zone, payload_type); } #endif // defined(TARGET_ARCH_ARM64) // Location for the result of a C signature function. static const NativeLocation& ResultLocation(Zone* zone, const NativeType& payload_type) { const auto& payload_type_converted = ConvertIfSoftFp(zone, payload_type); const auto& container_type = CallingConventions::kReturnRegisterExtension == kExtendedTo4 ? payload_type_converted.WidenTo4Bytes(zone) : payload_type_converted; if (container_type.IsFloat()) { return *new (zone) NativeFpuRegistersLocation( payload_type, container_type, CallingConventions::kReturnFpuReg); } if (container_type.IsInt() || container_type.IsVoid()) { if (container_type.SizeInBytes() == 8 && target::kWordSize == 4) { return *new (zone) NativeRegistersLocation( zone, payload_type, container_type, CallingConventions::kReturnReg, CallingConventions::kSecondReturnReg); } ASSERT(container_type.SizeInBytes() <= target::kWordSize); return *new (zone) NativeRegistersLocation( zone, payload_type, container_type, CallingConventions::kReturnReg); } // Compounds are laid out differently per ABI, so they are implemented // per ABI. const auto& compound_type = payload_type.AsCompound(); return CompoundResultLocation(zone, compound_type); } const NativeCallingConvention& NativeCallingConvention::FromSignature( Zone* zone, const NativeFunctionType& signature) { // With struct return values, a possible pointer to a return value can // occupy an argument position. Hence, allocate return value first. const auto& return_location = ResultLocation(zone, signature.return_type()); const auto& argument_locations = ArgumentLocations(zone, signature.argument_types(), return_location); return *new (zone) NativeCallingConvention(argument_locations, return_location); } intptr_t NativeCallingConvention::StackTopInBytes() const { const intptr_t num_arguments = argument_locations_.length(); intptr_t max_height_in_bytes = 0; for (intptr_t i = 0; i < num_arguments; i++) { max_height_in_bytes = Utils::Maximum( max_height_in_bytes, argument_locations_[i]->StackTopInBytes()); } return Utils::RoundUp(max_height_in_bytes, compiler::target::kWordSize); } void NativeCallingConvention::PrintTo(BaseTextBuffer* f, bool multi_line) const { if (!multi_line) { f->AddString("("); } for (intptr_t i = 0; i < argument_locations_.length(); i++) { if (i > 0) { if (multi_line) { f->AddString("\n"); } else { f->AddString(", "); } } argument_locations_[i]->PrintTo(f); } if (multi_line) { f->AddString("\n=>\n"); } else { f->AddString(") => "); } return_location_.PrintTo(f); if (multi_line) { f->AddString("\n"); } } const char* NativeCallingConvention::ToCString(Zone* zone, bool multi_line) const { ZoneTextBuffer textBuffer(zone); PrintTo(&textBuffer, multi_line); return textBuffer.buffer(); } #if !defined(FFI_UNIT_TESTS) const char* NativeCallingConvention::ToCString(bool multi_line) const { return ToCString(Thread::Current()->zone(), multi_line); } #endif } // namespace ffi } // namespace compiler } // namespace dart