| // 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 and vararg calls, floats and doubles get passed in integer |
| // registers. |
| static bool SoftFpAbi(bool has_varargs, bool is_result) { |
| #if defined(TARGET_ARCH_ARM) |
| if (has_varargs) { |
| return true; |
| } |
| return !TargetCPUFeatures::hardfp_supported(); |
| #elif defined(TARGET_ARCH_ARM64) && defined(DART_TARGET_OS_WINDOWS) |
| return has_varargs && !is_result; |
| #else |
| return false; |
| #endif |
| } |
| #else // !defined(FFI_UNIT_TESTS) |
| static bool SoftFpAbi(bool has_varargs, bool is_result) { |
| #if defined(TARGET_ARCH_ARM) && defined(DART_TARGET_OS_ANDROID) |
| return true; |
| #elif defined(TARGET_ARCH_ARM) |
| return has_varargs; |
| #elif defined(TARGET_ARCH_ARM64) && defined(DART_TARGET_OS_WINDOWS) |
| return has_varargs && !is_result; |
| #else |
| return false; |
| #endif |
| } |
| #endif // !defined(FFI_UNIT_TESTS) |
| |
| static const NativeType& ConvertFloatToInt(Zone* zone, const NativeType& type) { |
| ASSERT(type.IsFloat()); |
| if (type.SizeInBytes() == 4) { |
| return *new (zone) NativePrimitiveType(kInt32); |
| } |
| ASSERT(type.SizeInBytes() == 8); |
| return *new (zone) NativePrimitiveType(kInt64); |
| } |
| |
| // In Soft FP, floats are treated as 4 byte ints, and doubles as 8 byte ints. |
| static const NativeType& ConvertIfSoftFp(Zone* zone, |
| const NativeType& type, |
| bool has_varargs, |
| bool is_result = false) { |
| if (SoftFpAbi(has_varargs, is_result) && type.IsFloat()) { |
| return ConvertFloatToInt(zone, type); |
| } |
| return type; |
| } |
| |
| static PrimitiveType TypeForSize(intptr_t size) { |
| switch (size) { |
| case 8: |
| return kUint64; |
| case 7: |
| return kUint56; |
| case 6: |
| return kUint48; |
| case 5: |
| return kUint40; |
| case 4: |
| return kUint32; |
| case 3: |
| return kUint24; |
| case 2: |
| return kUint16; |
| case 1: |
| return kUint8; |
| default: |
| UNREACHABLE(); |
| return kVoid; |
| } |
| } |
| |
| // 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, bool has_varargs) |
| : has_varargs_(has_varargs), zone_(zone) {} |
| |
| const NativeLocation& AllocateArgumentVariadic(const NativeType& payload_type, |
| bool is_first_vararg = false, |
| bool is_vararg = false) { |
| #if defined(TARGET_ARCH_ARM64) && \ |
| (defined(DART_TARGET_OS_MACOS_IOS) || defined(DART_TARGET_OS_MACOS)) |
| if (is_first_vararg) { |
| // Block all registers. |
| BlockAllFpuRegisters(); |
| cpu_regs_used = CallingConventions::kNumArgRegs; |
| } |
| #endif |
| #if defined(TARGET_ARCH_RISCV64) || defined(TARGET_ARCH_RISCV32) |
| if (is_first_vararg) { |
| // Block all FPU registers. |
| BlockAllFpuRegisters(); |
| } |
| #endif |
| const auto& result = AllocateArgument(payload_type, is_vararg); |
| #if defined(TARGET_ARCH_X64) && defined(DART_TARGET_OS_WINDOWS) |
| if (has_varargs_) { |
| if (result.IsRegisters()) { |
| // If an integer register is used, block the corresponding xmm register. |
| ASSERT(CallingConventions::kArgumentIntRegXorFpuReg); |
| } else if (result.IsFpuRegisters()) { |
| // If an xmm register is used, also the corresponding integer register. |
| ASSERT(CallingConventions::kArgumentIntRegXorFpuReg); |
| const auto& fpu_reg_location = result.AsFpuRegisters(); |
| const FpuRegisterKind kind = kQuadFpuReg; |
| ASSERT(fpu_reg_location.fpu_reg_kind() == kind); |
| FpuRegister fpu_register = fpu_reg_location.fpu_reg(); |
| const intptr_t reg_index = fpu_register; |
| ASSERT(cpu_regs_used == reg_index + 1); |
| Register cpu_register = |
| CallingConventions::ArgumentRegisters[reg_index]; |
| const auto& container_type = ConvertFloatToInt(zone_, payload_type); |
| const auto& cpu_reg_location = *new (zone_) NativeRegistersLocation( |
| zone_, payload_type, container_type, cpu_register); |
| return *new (zone_) |
| BothNativeLocations(fpu_reg_location, cpu_reg_location); |
| } |
| } |
| #endif |
| return result; |
| } |
| |
| private: |
| const NativeLocation& AllocateArgument(const NativeType& payload_type, |
| |
| bool is_vararg = false) { |
| const auto& payload_type_converted = |
| ConvertIfSoftFp(zone_, payload_type, has_varargs_); |
| if (payload_type_converted.IsFloat()) { |
| return AllocateFloat(payload_type, is_vararg); |
| } |
| if (payload_type_converted.IsInt()) { |
| return AllocateInt(payload_type, is_vararg); |
| } |
| |
| // 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, is_vararg, /*is_result*/ false); |
| } |
| |
| const NativeLocation& AllocateFloat(const NativeType& payload_type, |
| bool is_vararg) { |
| 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++; |
| } |
| #if defined(TARGET_ARCH_ARM) |
| if (kind == kSingleFpuReg) { |
| return *new (zone_) |
| NativeFpuRegistersLocation(payload_type, payload_type, kind, |
| static_cast<SRegister>(reg_index)); |
| } |
| if (kind == kDoubleFpuReg) { |
| return *new (zone_) |
| NativeFpuRegistersLocation(payload_type, payload_type, kind, |
| static_cast<DRegister>(reg_index)); |
| } |
| #endif |
| ASSERT(kind == kQuadFpuReg); |
| FpuRegister reg = CallingConventions::FpuArgumentRegisters[reg_index]; |
| return *new (zone_) |
| NativeFpuRegistersLocation(payload_type, payload_type, reg); |
| } |
| |
| #if defined(TARGET_ARCH_RISCV64) |
| // After using up F registers, start bitcasting to X registers. |
| if (HasAvailableCpuRegisters(1)) { |
| const Register reg = AllocateCpuRegister(); |
| const auto& container_type = ConvertFloatToInt(zone_, payload_type); |
| return *new (zone_) |
| NativeRegistersLocation(zone_, payload_type, container_type, reg); |
| } |
| #elif defined(TARGET_ARCH_RISCV32) |
| // After using up F registers, start bitcasting to X register (pairs). |
| if (((payload_type.SizeInBytes() == 4) && HasAvailableCpuRegisters(1)) || |
| ((payload_type.SizeInBytes() == 8) && HasAvailableCpuRegisters(2))) { |
| const auto& container_type = ConvertFloatToInt(zone_, payload_type); |
| return AllocateInt(payload_type, container_type, is_vararg); |
| } |
| #endif |
| |
| BlockAllFpuRegisters(); |
| if (CallingConventions::kArgumentIntRegXorFpuReg) { |
| ASSERT(cpu_regs_used == CallingConventions::kNumArgRegs); |
| } |
| return AllocateStack(payload_type); |
| } |
| |
| const NativeLocation& AllocateInt(const NativeType& payload_type, |
| const NativeType& container_type, |
| bool is_vararg) { |
| if (target::kWordSize == 4 && payload_type.SizeInBytes() == 8) { |
| if (CallingConventions::kArgumentRegisterAlignment == |
| kAlignedToWordSizeAndValueSize || |
| (is_vararg && CallingConventions::kArgumentRegisterAlignmentVarArgs == |
| kAlignedToWordSizeAndValueSize)) { |
| 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, is_vararg); |
| } |
| |
| // Constructs a container type. |
| const NativeLocation& AllocateInt(const NativeType& payload_type, |
| bool is_vararg) { |
| const auto& payload_type_converted = |
| ConvertIfSoftFp(zone_, payload_type, has_varargs_); |
| |
| // Some calling conventions require the callee to make the lowest 32 bits |
| // in registers non-garbage. |
| const auto& container_type = payload_type_converted.Extend( |
| zone_, CallingConventions::kArgumentRegisterExtension); |
| |
| return AllocateInt(payload_type, container_type, is_vararg); |
| } |
| |
| #if defined(TARGET_ARCH_X64) && !defined(DART_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, |
| bool is_vararg, |
| bool is_result) { |
| 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<intptr_t>(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& payload_type = |
| *new (zone_) NativePrimitiveType(TypeForSize(Utils::Minimum( |
| size - offset, compiler::target::kWordSize))); |
| const auto& container_type = *new (zone_) NativePrimitiveType( |
| TypeForSize(compiler::target::kWordSize)); |
| multiple_locations.Add(new (zone_) NativeRegistersLocation( |
| zone_, payload_type, container_type, AllocateCpuRegister())); |
| } |
| } |
| return *new (zone_) |
| MultipleNativeLocations(payload_type, multiple_locations); |
| } |
| } |
| return AllocateStack(payload_type); |
| } |
| #endif // defined(TARGET_ARCH_X64) && !defined(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_X64) && defined(DART_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, |
| bool is_vararg, |
| bool is_result) { |
| 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(kAddress); |
| 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(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_IA32) |
| const NativeLocation& AllocateCompound(const NativeCompoundType& payload_type, |
| bool is_vararg, |
| bool is_result) { |
| return AllocateStack(payload_type); |
| } |
| #endif // defined(TARGET_ARCH_IA32) |
| |
| #if defined(TARGET_ARCH_ARM) |
| // Transfer homogeneous 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, |
| bool is_vararg, |
| bool is_result) { |
| const auto& compound_type = payload_type.AsCompound(); |
| if (compound_type.ContainsHomogeneousFloats() && |
| !SoftFpAbi(has_varargs_, is_result) && |
| 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 if (payload_type.AlignmentInBytesStack() == 8) { |
| const intptr_t chunk_size = payload_type.AlignmentInBytesStack(); |
| ASSERT(chunk_size == 4 || chunk_size == 8); |
| const intptr_t size_rounded = |
| Utils::RoundUp(payload_type.SizeInBytes(), chunk_size); |
| const intptr_t num_chunks = size_rounded / chunk_size; |
| const auto& chuck_type = |
| *new (zone_) NativePrimitiveType(chunk_size == 4 ? kInt32 : kInt64); |
| |
| NativeLocations& multiple_locations = |
| *new (zone_) NativeLocations(zone_, num_chunks); |
| for (int i = 0; i < num_chunks; 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_chunks - 1 && chunk_size == 8 && |
| Utils::RoundUp(payload_type.SizeInBytes(), 4) % 8 == 4) { |
| const auto& small_chuck_type = *new (zone_) NativePrimitiveType( |
| chunk_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); |
| } else { |
| return AllocateCompoundAsMultiple(compound_type); |
| } |
| } |
| #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, |
| bool is_vararg, |
| bool is_result) { |
| const auto& compound_type = payload_type.AsCompound(); |
| const intptr_t size = compound_type.SizeInBytes(); |
| if (compound_type.ContainsHomogeneousFloats() && |
| !SoftFpAbi(has_varargs_, is_result) && |
| 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, is_vararg); |
| } |
| |
| if (size <= 16) { |
| const intptr_t size_rounded = Utils::RoundUp(size, 8); |
| const intptr_t num_chunks = size_rounded / 8; |
| ASSERT((num_chunks == 1) || (num_chunks == 2)); |
| |
| // All-or-none: block any leftover registers. |
| #if defined(DART_TARGET_OS_WINDOWS) |
| if (!HasAvailableCpuRegisters(num_chunks) && !is_vararg) { |
| cpu_regs_used = CallingConventions::kNumArgRegs; |
| } |
| #else |
| if (!HasAvailableCpuRegisters(num_chunks)) { |
| cpu_regs_used = CallingConventions::kNumArgRegs; |
| } |
| #endif |
| |
| return AllocateCompoundAsMultiple(payload_type); |
| } |
| |
| const auto& pointer_location = |
| AllocateArgument(*new (zone_) NativePrimitiveType(kInt64)); |
| return *new (zone_) |
| PointerToMemoryLocation(pointer_location, compound_type); |
| } |
| #endif // defined(TARGET_ARCH_ARM64) |
| |
| #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) |
| // See RISC-V ABIs Specification |
| // https://github.com/riscv-non-isa/riscv-elf-psabi-doc/releases |
| const NativeLocation& AllocateCompound(const NativeCompoundType& payload_type, |
| bool is_vararg, |
| bool is_result) { |
| const auto& compound_type = payload_type.AsCompound(); |
| |
| // 2.2. Hardware Floating-point Calling Convention. |
| const NativePrimitiveType* first = nullptr; |
| const NativePrimitiveType* second = nullptr; |
| const intptr_t num_primitive_members = |
| compound_type.PrimitivePairMembers(&first, &second); |
| |
| // If exactly one floating-point member, pass like a scalar. |
| if ((num_primitive_members == 1) && first->IsFloat()) { |
| NativeLocations& multiple_locations = |
| *new (zone_) NativeLocations(zone_, 1); |
| multiple_locations.Add(&AllocateArgument(*first)); |
| return *new (zone_) |
| MultipleNativeLocations(compound_type, multiple_locations); |
| } |
| |
| if (num_primitive_members == 2) { |
| if (first->IsFloat() && second->IsFloat()) { |
| // If exactly two floating-point members, pass like two scalars if two F |
| // registers are available. |
| if (HasAvailableFpuRegisters(2)) { |
| NativeLocations& multiple_locations = |
| *new (zone_) NativeLocations(zone_, 2); |
| multiple_locations.Add(&AllocateArgument(*first)); |
| multiple_locations.Add(&AllocateArgument(*second)); |
| return *new (zone_) |
| MultipleNativeLocations(compound_type, multiple_locations); |
| } |
| } else if (first->IsFloat() || second->IsFloat()) { |
| // If exactly two members, one is integer and one is float in either |
| // order, pass like two scalars if both an X and F register are |
| // available. |
| if (HasAvailableFpuRegisters(1) && HasAvailableCpuRegisters(1)) { |
| NativeLocations& multiple_locations = |
| *new (zone_) NativeLocations(zone_, 2); |
| multiple_locations.Add(&AllocateArgument(*first)); |
| multiple_locations.Add(&AllocateArgument(*second)); |
| return *new (zone_) |
| MultipleNativeLocations(compound_type, multiple_locations); |
| } |
| } |
| } |
| |
| // 2.1. Integer Calling Convention. |
| // If total size is <= XLEN, passed like an XLEN scalar: use a register if |
| // available or pass by value on the stack. |
| // If total size is <= 2*XLEN, passed like two XLEN scalars: use registers |
| // if available or pass by value on the stack. If only one register is |
| // available, pass the low part by register and the high part on the |
| // stack. |
| if (compound_type.SizeInBytes() <= 2 * target::kWordSize) { |
| return AllocateCompoundAsMultiple(compound_type); |
| } |
| |
| // Otherwise, passed by reference. |
| const auto& pointer_type = *new (zone_) NativePrimitiveType(kAddress); |
| const auto& pointer_location = AllocateArgument(pointer_type); |
| return *new (zone_) |
| PointerToMemoryLocation(pointer_location, compound_type); |
| } |
| #endif |
| |
| // Allocate in word-sized chunks, with the container as a full word-sized |
| // register or stack slot and the payload constrained to the struct's size. |
| // |
| // Note this describes the location at call. Consumes of this location, such |
| // as FfiCallConvertCompoundArgumentToNative or EmitReturnMoves, often assume |
| // the location of the source compound in the heap corresponds to this |
| // location with just a change in base register. This is often true, except |
| // some ABIs assume zero extension of the last chunk, so the stack location at |
| // call is bigger than the location in the heap. Here we set the container |
| // size to reflect that zero-extended stack slot and rely on loads during |
| // moves opting to use the payload size instead of the container size to stay |
| // in-bounds. |
| const NativeLocation& AllocateCompoundAsMultiple( |
| const NativeCompoundType& compound_type) { |
| const intptr_t chunk_size = compiler::target::kWordSize; |
| const intptr_t num_chunks = |
| Utils::RoundUp(compound_type.SizeInBytes(), chunk_size) / chunk_size; |
| const NativeType& container_type = |
| *new (zone_) NativePrimitiveType(TypeForSize(chunk_size)); |
| NativeLocations& locations = |
| *new (zone_) NativeLocations(zone_, num_chunks); |
| intptr_t size_remaining = compound_type.SizeInBytes(); |
| while (size_remaining > 0) { |
| const auto& chunk = AllocateArgument(container_type); |
| |
| const intptr_t size = Utils::Minimum(size_remaining, chunk_size); |
| const NativeType& payload_type = |
| *new (zone_) NativePrimitiveType(TypeForSize(size)); |
| locations.Add( |
| &chunk.WithOtherNativeType(zone_, payload_type, container_type)); |
| size_remaining -= size; |
| } |
| return *new (zone_) MultipleNativeLocations(compound_type, locations); |
| } |
| |
| 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, |
| bool is_vararg = false) { |
| align_stack(payload_type.AlignmentInBytesStack(is_vararg)); |
| 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 = |
| payload_type.Extend(zone_, CallingConventions::kArgumentStackExtension); |
| const auto& result = *new (zone_) NativeStackLocation( |
| payload_type, container_type, CallingConventions::kStackPointerRegister, |
| stack_height_in_bytes); |
| stack_height_in_bytes += size; |
| align_stack(payload_type.AlignmentInBytesStack(is_vararg)); |
| return result; |
| } |
| |
| void align_stack(intptr_t alignment) { |
| stack_height_in_bytes = Utils::RoundUp(stack_height_in_bytes, alignment); |
| } |
| |
| int NumFpuRegisters(FpuRegisterKind kind) const { |
| #if defined(TARGET_ARCH_ARM) |
| if (has_varargs_) 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 { |
| 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; |
| } |
| |
| bool HasAvailableCpuRegisters(intptr_t count) const { |
| return cpu_regs_used + count <= CallingConventions::kNumArgRegs; |
| } |
| bool HasAvailableFpuRegisters(intptr_t count) const { |
| return FirstFreeFpuRegisterIndex(kQuadFpuReg, count) != kNoFpuRegister; |
| } |
| |
| 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; |
| const bool has_varargs_; |
| Zone* zone_; |
| }; |
| |
| // Location for the arguments of a C signature function. |
| static NativeLocations& ArgumentLocations( |
| Zone* zone, |
| const ZoneGrowableArray<const NativeType*>& arg_reps, |
| const NativeLocation& return_location, |
| intptr_t var_args_index) { |
| 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. |
| const bool has_varargs = |
| var_args_index != NativeFunctionType::kNoVariadicArguments; |
| ArgumentAllocator frame_state(zone, has_varargs); |
| #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.AllocateArgumentVariadic(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]; |
| const bool is_first_vararg = has_varargs && i == var_args_index; |
| const bool is_vararg = has_varargs && i >= var_args_index; |
| result.Add( |
| &frame_state.AllocateArgumentVariadic(rep, is_first_vararg, is_vararg)); |
| } |
| return result; |
| } |
| |
| #if !defined(TARGET_ARCH_IA32) |
| static const NativeLocation& PointerToMemoryResultLocation( |
| Zone* zone, |
| const NativeCompoundType& payload_type) { |
| const auto& pointer_type = *new (zone) NativePrimitiveType(kAddress); |
| 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(kAddress); |
| 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(DART_TARGET_OS_WINDOWS) |
| static const NativeLocation& CompoundResultLocation( |
| Zone* zone, |
| const NativeCompoundType& payload_type, |
| bool has_varargs) { |
| 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 bool first_half_in_xmm = payload_type.ContainsOnlyFloats( |
| Range::StartAndEnd(0, Utils::Minimum<intptr_t>(size, 8))); |
| if (first_half_in_xmm) { |
| multiple_locations.Add(new (zone) NativeFpuRegistersLocation( |
| double_type, double_type, kQuadFpuReg, |
| CallingConventions::kReturnFpuReg)); |
| used_xmm_regs++; |
| } else { |
| const auto& payload_type = *new (zone) NativePrimitiveType( |
| TypeForSize(Utils::Minimum(size, compiler::target::kWordSize))); |
| const auto& container_type = *new (zone) NativePrimitiveType( |
| TypeForSize(compiler::target::kWordSize)); |
| multiple_locations.Add(new (zone) NativeRegistersLocation( |
| zone, payload_type, container_type, CallingConventions::kReturnReg)); |
| used_regs++; |
| } |
| if (size > 8) { |
| const bool second_half_in_xmm = payload_type.ContainsOnlyFloats( |
| Range::StartAndEnd(8, Utils::Minimum<intptr_t>(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; |
| const auto& payload_type = *new (zone) NativePrimitiveType( |
| TypeForSize(Utils::Minimum(size - 8, compiler::target::kWordSize))); |
| const auto& container_type = *new (zone) NativePrimitiveType( |
| TypeForSize(compiler::target::kWordSize)); |
| multiple_locations.Add(new (zone) NativeRegistersLocation( |
| zone, payload_type, container_type, reg)); |
| used_regs++; |
| } |
| } |
| return *new (zone) |
| MultipleNativeLocations(payload_type, multiple_locations); |
| } |
| return PointerToMemoryResultLocation(zone, payload_type); |
| } |
| #endif // defined(TARGET_ARCH_X64) && !defined(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_X64) && defined(DART_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, |
| bool has_varargs) { |
| 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(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_IA32) && !defined(DART_TARGET_OS_WINDOWS) |
| static const NativeLocation& CompoundResultLocation( |
| Zone* zone, |
| const NativeCompoundType& payload_type, |
| bool has_varargs) { |
| return PointerToMemoryResultLocation(zone, payload_type); |
| } |
| #endif // defined(TARGET_ARCH_IA32) && !defined(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_IA32) && defined(DART_TARGET_OS_WINDOWS) |
| // Windows uses up to two return registers, while Linux does not. |
| static const NativeLocation& CompoundResultLocation( |
| Zone* zone, |
| const NativeCompoundType& payload_type, |
| bool has_varargs) { |
| 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(DART_TARGET_OS_WINDOWS) |
| |
| #if defined(TARGET_ARCH_ARM) |
| // Arm passes homogenous float return values in FPU registers and small |
| // composites 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, |
| bool has_varargs) { |
| const intptr_t num_members = payload_type.NumPrimitiveMembersRecursive(); |
| if (payload_type.ContainsHomogeneousFloats() && |
| !SoftFpAbi(has_varargs, /*is_result*/ true) && 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, |
| bool has_varargs) { |
| ArgumentAllocator frame_state(zone, has_varargs); |
| const auto& location_as_argument = |
| frame_state.AllocateArgumentVariadic(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) |
| |
| #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) |
| static const NativeLocation& CompoundResultLocation( |
| Zone* zone, |
| const NativeCompoundType& payload_type, |
| bool has_varargs) { |
| // First or first and second argument registers if it fits, otherwise a |
| // pointer to the result location is passed in. |
| ArgumentAllocator frame_state(zone, has_varargs); |
| const auto& location_as_argument = |
| frame_state.AllocateArgumentVariadic(payload_type); |
| if (!location_as_argument.IsStack() && |
| !location_as_argument.IsPointerToMemory()) { |
| return location_as_argument; |
| } |
| return PointerToMemoryResultLocation(zone, payload_type); |
| } |
| #endif // defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) |
| |
| // Location for the result of a C signature function. |
| static const NativeLocation& ResultLocation(Zone* zone, |
| const NativeType& payload_type, |
| bool has_varargs) { |
| const auto& payload_type_converted = |
| ConvertIfSoftFp(zone, payload_type, has_varargs, /*is_result*/ true); |
| const auto& container_type = payload_type_converted.Extend( |
| zone, CallingConventions::kReturnRegisterExtension); |
| |
| 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, has_varargs); |
| } |
| |
| const NativeCallingConvention& NativeCallingConvention::FromSignature( |
| Zone* zone, |
| const NativeFunctionType& signature) { |
| const bool contains_varargs = signature.variadic_arguments_index() != |
| NativeFunctionType::kNoVariadicArguments; |
| // 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(), contains_varargs); |
| const auto& argument_locations = |
| ArgumentLocations(zone, signature.argument_types(), return_location, |
| signature.variadic_arguments_index()); |
| return *new (zone) NativeCallingConvention(argument_locations, |
| return_location, contains_varargs); |
| } |
| |
| 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()); |
| } |
| if (return_location_.IsPointerToMemory()) { |
| const auto& ret_loc = return_location_.AsPointerToMemory(); |
| max_height_in_bytes = |
| Utils::Maximum(max_height_in_bytes, ret_loc.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 |