Skip to content

Commit 1854bb0

Browse files
committed
[vm/ffi] Support varargs in the backend
On ARM64 macos and ios, when varargs are used, the first vararg blocks all cpu and fpu registers. On Windows x64, when varargs are used, floating point arguments are passed _both_ in the integer and double register. The Windows logic requires a new kind of native location: `BothNativeLocations`, which signals that a value needs to be copied to both locations before an FFI call, and can be copied from any of the two locations when getting an FFI callback. TEST=runtime/vm/compiler/ffi/unit_tests/variadic_double/x64_win.expect Note that integer arguments already block out the corresponding xmm registers on Windows x64. On System-V, an upper bound of the number of XMM registers used must be passed in AL. (Not reflected in the unit tests here, but will be in the dependent CL.) On ARM (32 bit), using varargs forces the calling convention to be in softfp mode even on hardfp supported devices. On RISC-V, the FPU registers are blocked when using varargs. TEST=runtime/vm/compiler/ffi/native_calling_convention_test.cc Test outputs in: runtime/vm/compiler/ffi/unit_tests/variadic_* Run test with `tools/test.py ffi_unit`. Bug: #38578 Change-Id: Ic568f8156c1c28ac3d6a2144805edf8caaa0169c Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278342 Reviewed-by: Ryan Macnak <[email protected]>
1 parent a43bada commit 1854bb0

File tree

121 files changed

+1307
-115
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+1307
-115
lines changed

runtime/vm/compiler/ffi/native_calling_convention.cc

Lines changed: 175 additions & 86 deletions
Large diffs are not rendered by default.

runtime/vm/compiler/ffi/native_calling_convention.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class NativeCallingConvention : public ZoneAllocated {
3636
return argument_locations_;
3737
}
3838
const NativeLocation& return_location() const { return return_location_; }
39+
bool contains_varargs() const { return contains_varargs_; }
3940

4041
intptr_t StackTopInBytes() const;
4142

@@ -48,12 +49,15 @@ class NativeCallingConvention : public ZoneAllocated {
4849

4950
private:
5051
NativeCallingConvention(const NativeLocations& argument_locations,
51-
const NativeLocation& return_location)
52+
const NativeLocation& return_location,
53+
bool contains_varargs)
5254
: argument_locations_(argument_locations),
53-
return_location_(return_location) {}
55+
return_location_(return_location),
56+
contains_varargs_(contains_varargs) {}
5457

5558
const NativeLocations& argument_locations_;
5659
const NativeLocation& return_location_;
60+
const bool contains_varargs_;
5761
};
5862

5963
} // namespace ffi

runtime/vm/compiler/ffi/native_calling_convention_test.cc

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ namespace ffi {
1414
const NativeCallingConvention& RunSignatureTest(
1515
dart::Zone* zone,
1616
const char* name,
17-
const NativeTypes& argument_types,
18-
const NativeType& return_type) {
19-
const auto& native_signature =
20-
*new (zone) NativeFunctionType(argument_types, return_type);
21-
17+
const NativeFunctionType& native_signature) {
2218
const auto& native_calling_convention =
2319
NativeCallingConvention::FromSignature(zone, native_signature);
2420

@@ -47,6 +43,17 @@ const NativeCallingConvention& RunSignatureTest(
4743
return native_calling_convention;
4844
}
4945

46+
const NativeCallingConvention& RunSignatureTest(
47+
dart::Zone* zone,
48+
const char* name,
49+
const NativeTypes& argument_types,
50+
const NativeType& return_type) {
51+
const auto& native_signature =
52+
*new (zone) NativeFunctionType(argument_types, return_type);
53+
54+
return RunSignatureTest(zone, name, native_signature);
55+
}
56+
5057
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_int8x10) {
5158
const auto& int8type = *new (Z) NativePrimitiveType(kInt8);
5259

@@ -729,6 +736,176 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_regress_fuchsia105336) {
729736
RunSignatureTest(Z, "regress_fuchsia105336", arguments, void_type);
730737
}
731738

739+
// Binding in Dart with variadic arguments:
740+
// `IntPtr Function(IntPtr, VarArgs<(IntPtr, IntPtr, IntPtr, IntPtr)>)`
741+
//
742+
// See the *.expect in ./unit_tests for this behavior.
743+
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_variadic_int) {
744+
#if defined(TARGET_ARCH_IS_32_BIT)
745+
const auto& intptr_type = *new (Z) NativePrimitiveType(kInt32);
746+
#elif defined(TARGET_ARCH_IS_64_BIT)
747+
const auto& intptr_type = *new (Z) NativePrimitiveType(kInt64);
748+
#endif
749+
750+
auto& arguments = *new (Z) NativeTypes(Z, 5);
751+
arguments.Add(&intptr_type);
752+
arguments.Add(&intptr_type);
753+
arguments.Add(&intptr_type);
754+
arguments.Add(&intptr_type);
755+
arguments.Add(&intptr_type);
756+
757+
const auto& native_signature = *new (Z) NativeFunctionType(
758+
arguments, intptr_type, /*variadic_arguments_index=*/1);
759+
760+
RunSignatureTest(Z, "variadic_int", native_signature);
761+
}
762+
763+
// Binding in Dart with variadic arguments:
764+
// `Double Function(Double, VarArgs<(Double, Double, Double, Double)>)`
765+
//
766+
// See the *.expect in ./unit_tests for this behavior.
767+
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_variadic_double) {
768+
const auto& double_type = *new (Z) NativePrimitiveType(kDouble);
769+
770+
auto& arguments = *new (Z) NativeTypes(Z, 5);
771+
arguments.Add(&double_type);
772+
arguments.Add(&double_type);
773+
arguments.Add(&double_type);
774+
arguments.Add(&double_type);
775+
arguments.Add(&double_type);
776+
777+
const auto& native_signature = *new (Z) NativeFunctionType(
778+
arguments, double_type, /*variadic_arguments_index=*/1);
779+
780+
RunSignatureTest(Z, "variadic_double", native_signature);
781+
}
782+
783+
// Binding in Dart with variadic arguments:
784+
// `Double Function(Double, VarArgs<(Struct20BytesHomogeneousFloat, Double)>)`
785+
//
786+
// See the *.expect in ./unit_tests for this behavior.
787+
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_variadic_with_struct) {
788+
const auto& double_type = *new (Z) NativePrimitiveType(kDouble);
789+
const auto& float_type = *new (Z) NativePrimitiveType(kFloat);
790+
791+
auto& member_types = *new (Z) NativeTypes(Z, 5);
792+
member_types.Add(&float_type);
793+
member_types.Add(&float_type);
794+
member_types.Add(&float_type);
795+
member_types.Add(&float_type);
796+
member_types.Add(&float_type);
797+
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);
798+
799+
auto& arguments = *new (Z) NativeTypes(Z, 3);
800+
arguments.Add(&double_type);
801+
arguments.Add(&struct_type);
802+
arguments.Add(&double_type);
803+
804+
const auto& native_signature = *new (Z) NativeFunctionType(
805+
arguments, double_type, /*variadic_arguments_index=*/1);
806+
807+
RunSignatureTest(Z, "variadic_with_struct", native_signature);
808+
}
809+
810+
// Binding in Dart with variadic arguments.
811+
//
812+
// Especially macos_arm64 is interesting due to stack alignment.
813+
//
814+
// See the *.expect in ./unit_tests for this behavior.
815+
UNIT_TEST_CASE_WITH_ZONE(
816+
NativeCallingConvention_variadic_with_homogenous_struct) {
817+
const auto& double_type = *new (Z) NativePrimitiveType(kDouble);
818+
const auto& float_type = *new (Z) NativePrimitiveType(kFloat);
819+
const auto& int64_type = *new (Z) NativePrimitiveType(kInt64);
820+
const auto& int32_type = *new (Z) NativePrimitiveType(kInt32);
821+
822+
auto& member_types = *new (Z) NativeTypes(Z, 3);
823+
member_types.Add(&float_type);
824+
member_types.Add(&float_type);
825+
member_types.Add(&float_type);
826+
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);
827+
828+
auto& arguments = *new (Z) NativeTypes(Z, 13);
829+
arguments.Add(&double_type);
830+
arguments.Add(&double_type);
831+
arguments.Add(&double_type);
832+
arguments.Add(&double_type);
833+
arguments.Add(&double_type);
834+
arguments.Add(&double_type);
835+
arguments.Add(&double_type);
836+
arguments.Add(&double_type); // Exhaust FPU registers
837+
arguments.Add(&float_type); // Misalign stack.
838+
arguments.Add(
839+
&struct_type); // Homogenous struct, not aligned to wordsize on stack.
840+
arguments.Add(&int64_type); // Start varargs.
841+
arguments.Add(&int32_type); // Misalign stack again.
842+
arguments.Add(
843+
&struct_type); // Homogenous struct, aligned to wordsize on stack.
844+
845+
const auto& native_signature = *new (Z) NativeFunctionType(
846+
arguments, double_type, /*variadic_arguments_index=*/11);
847+
848+
RunSignatureTest(Z, "variadic_with_homogenous_struct", native_signature);
849+
}
850+
851+
// Binding in Dart with variadic arguments.
852+
//
853+
// Especially linux_riscv32 is interesting due to register alignment.
854+
//
855+
// See the *.expect in ./unit_tests for this behavior.
856+
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_variadic_register_alignment) {
857+
const auto& double_type = *new (Z) NativePrimitiveType(kDouble);
858+
859+
auto& member_types = *new (Z) NativeTypes(Z, 4);
860+
member_types.Add(&double_type);
861+
member_types.Add(&double_type);
862+
member_types.Add(&double_type);
863+
member_types.Add(&double_type);
864+
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);
865+
866+
auto& arguments = *new (Z) NativeTypes(Z, 13);
867+
arguments.Add(&double_type);
868+
arguments.Add(&double_type); // Passed in int register pair on RISC-V 32.
869+
arguments.Add(
870+
&struct_type); // Passed using single integer register on RISC-V 32.
871+
arguments.Add(
872+
&double_type); // Passed in _aligned_ int register pair on RISC-V 32.
873+
874+
const auto& native_signature = *new (Z) NativeFunctionType(
875+
arguments, double_type, /*variadic_arguments_index=*/1);
876+
877+
RunSignatureTest(Z, "variadic_register_alignment", native_signature);
878+
}
879+
880+
// Variadic function in C:
881+
// `int ioctl(int, unsigned long, ...)`
882+
//
883+
// Binding in Dart with single variadic argument:
884+
// `Int32 Function(Int32, Int64, VarArgs<Pointer<Void>>)`
885+
//
886+
// https://github.com/dart-lang/sdk/issues/49460
887+
//
888+
// See the *.expect in ./unit_tests for this behavior.
889+
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_regress49460) {
890+
const auto& int32_type = *new (Z) NativePrimitiveType(kInt32);
891+
const auto& int64_type = *new (Z) NativePrimitiveType(kInt64);
892+
#if defined(TARGET_ARCH_IS_32_BIT)
893+
const auto& intptr_type = *new (Z) NativePrimitiveType(kInt32);
894+
#elif defined(TARGET_ARCH_IS_64_BIT)
895+
const auto& intptr_type = *new (Z) NativePrimitiveType(kInt64);
896+
#endif
897+
898+
auto& arguments = *new (Z) NativeTypes(Z, 3);
899+
arguments.Add(&int32_type);
900+
arguments.Add(&int64_type);
901+
arguments.Add(&intptr_type); // pointer
902+
903+
const auto& native_signature = *new (Z) NativeFunctionType(
904+
arguments, int32_type, /*variadic_arguments_index=*/2);
905+
906+
RunSignatureTest(Z, "regress49460", native_signature);
907+
}
908+
732909
} // namespace ffi
733910
} // namespace compiler
734911
} // namespace dart

runtime/vm/compiler/ffi/native_location.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ const PointerToMemoryLocation& NativeLocation::AsPointerToMemory() const {
9999
return static_cast<const PointerToMemoryLocation&>(*this);
100100
}
101101

102+
const BothNativeLocations& NativeLocation::AsBoth() const {
103+
ASSERT(IsBoth());
104+
return static_cast<const BothNativeLocations&>(*this);
105+
}
106+
102107
#if !defined(FFI_UNIT_TESTS)
103108
Location NativeRegistersLocation::AsLocation() const {
104109
ASSERT(IsExpressibleAsLocation());
@@ -354,6 +359,14 @@ void MultipleNativeLocations::PrintTo(BaseTextBuffer* f) const {
354359
PrintRepresentations(f, *this);
355360
}
356361

362+
void BothNativeLocations::PrintTo(BaseTextBuffer* f) const {
363+
f->Printf("B(");
364+
location0_.PrintTo(f);
365+
f->Printf(", ");
366+
location1_.PrintTo(f);
367+
f->Printf(")");
368+
}
369+
357370
#if !defined(FFI_UNIT_TESTS)
358371
const char* NativeLocation::ToCString() const {
359372
return ToCString(Thread::Current()->zone());

runtime/vm/compiler/ffi/native_location.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class NativeFpuRegistersLocation;
3232
class NativeStackLocation;
3333
class MultipleNativeLocations;
3434
class PointerToMemoryLocation;
35+
class BothNativeLocations;
3536

3637
// NativeLocation objects are used in the FFI to describe argument and return
3738
// value locations in all native ABIs that the FFI supports.
@@ -99,6 +100,7 @@ class NativeLocation : public ZoneAllocated {
99100
virtual bool IsStack() const { return false; }
100101
virtual bool IsMultiple() const { return false; }
101102
virtual bool IsPointerToMemory() const { return false; }
103+
virtual bool IsBoth() const { return false; }
102104

103105
virtual bool IsExpressibleAsLocation() const { return false; }
104106
#if !defined(FFI_UNIT_TESTS)
@@ -119,6 +121,7 @@ class NativeLocation : public ZoneAllocated {
119121
const NativeStackLocation& AsStack() const;
120122
const MultipleNativeLocations& AsMultiple() const;
121123
const PointerToMemoryLocation& AsPointerToMemory() const;
124+
const BothNativeLocations& AsBoth() const;
122125

123126
// Retrieve one part from this location when it is split into multiple parts.
124127
virtual NativeLocation& Split(Zone* zone,
@@ -454,6 +457,48 @@ class MultipleNativeLocations : public NativeLocation {
454457
DISALLOW_COPY_AND_ASSIGN(MultipleNativeLocations);
455458
};
456459

460+
// The location of a value that is in two locations.
461+
//
462+
// Should only happen on win_x64 with variadic arguments.
463+
class BothNativeLocations : public NativeLocation {
464+
public:
465+
BothNativeLocations(const NativeLocation& location0,
466+
const NativeLocation& location1)
467+
: NativeLocation(location0.payload_type(), location0.container_type()),
468+
location0_(location0),
469+
location1_(location1) {}
470+
virtual ~BothNativeLocations() {}
471+
472+
virtual bool IsBoth() const { return true; }
473+
474+
virtual void PrintTo(BaseTextBuffer* f) const;
475+
476+
virtual NativeLocation& WithOtherNativeType(
477+
Zone* zone,
478+
const NativeType& new_payload_type,
479+
const NativeType& new_container_type) const {
480+
UNREACHABLE();
481+
}
482+
483+
virtual intptr_t StackTopInBytes() const {
484+
// Only used with registers.
485+
return 0;
486+
}
487+
488+
const NativeLocation& location(intptr_t index) const {
489+
ASSERT(index == 0 || index == 1);
490+
if (index == 0) {
491+
return location0_;
492+
}
493+
return location1_;
494+
}
495+
496+
private:
497+
const NativeLocation& location0_;
498+
const NativeLocation& location1_;
499+
DISALLOW_COPY_AND_ASSIGN(BothNativeLocations);
500+
};
501+
457502
#if !defined(FFI_UNIT_TESTS)
458503
// Return a memory operand for stack slot locations.
459504
compiler::Address NativeLocationToStackSlotAddress(

runtime/vm/compiler/ffi/native_type.cc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ intptr_t NativePrimitiveType::SizeInBytes() const {
127127
return fundamental_size_in_bytes[representation_];
128128
}
129129

130-
intptr_t NativePrimitiveType::AlignmentInBytesStack() const {
130+
intptr_t NativePrimitiveType::AlignmentInBytesStack(bool is_vararg) const {
131131
switch (CallingConventions::kArgumentStackAlignment) {
132132
case kAlignedToWordSize:
133133
// The default is to align stack arguments to word size.
@@ -178,6 +178,7 @@ NativeStructType& NativeStructType::FromNativeTypes(Zone* zone,
178178
// If this struct is passed on the stack, it should be aligned to the largest
179179
// alignment of its members when passing those members on the stack.
180180
intptr_t alignment_stack = kAtLeast1ByteAligned;
181+
intptr_t alignment_stack_vararg = kAtLeast1ByteAligned;
181182
#if (defined(DART_TARGET_OS_MACOS_IOS) || defined(DART_TARGET_OS_MACOS)) && \
182183
defined(TARGET_ARCH_ARM64)
183184
// On iOS64 and MacOS arm64 stack values can be less aligned than wordSize,
@@ -193,6 +194,7 @@ NativeStructType& NativeStructType::FromNativeTypes(Zone* zone,
193194
if (!ContainsHomogeneousFloatsInternal(members)) {
194195
alignment_stack = compiler::target::kWordSize;
195196
}
197+
alignment_stack_vararg = compiler::target::kWordSize;
196198
#endif
197199

198200
auto& member_offsets =
@@ -212,11 +214,14 @@ NativeStructType& NativeStructType::FromNativeTypes(Zone* zone,
212214
offset += member_size;
213215
alignment_field = Utils::Maximum(alignment_field, member_align_field);
214216
alignment_stack = Utils::Maximum(alignment_stack, member_align_stack);
217+
alignment_stack_vararg =
218+
Utils::Maximum(alignment_stack_vararg, member_align_stack);
215219
}
216220
const intptr_t size = Utils::RoundUp(offset, alignment_field);
217221

218-
return *new (zone) NativeStructType(members, member_offsets, size,
219-
alignment_field, alignment_stack);
222+
return *new (zone)
223+
NativeStructType(members, member_offsets, size, alignment_field,
224+
alignment_stack, alignment_stack_vararg);
220225
}
221226

222227
// Keep consistent with
@@ -738,6 +743,9 @@ void NativeFunctionType::PrintTo(BaseTextBuffer* f) const {
738743
if (i > 0) {
739744
f->AddString(", ");
740745
}
746+
if (i == variadic_arguments_index_) {
747+
f->AddString("varargs: ");
748+
}
741749
argument_types_[i]->PrintTo(f);
742750
}
743751
f->AddString(") => ");

0 commit comments

Comments
 (0)