Skip to content

Commit

Permalink
[vm/ffi] Support Unions backend
Browse files Browse the repository at this point in the history
This Splits the NativeCompoundType into NativeStructType and
NativeUnionType.

The calling conventions themselves did not have to be updated at all,
they rely on the 'queries' on NativeTypes.

Some interesting corner cases in the native calling conventions are:
1. Arm64 and arm hardfp still regard unions as homogeneous floats, even
   if the union members are different sizes.
2. Unions can be larger than their members if their largest member has
   a smaller alignment than a smaller member.
3. When a specific range in a struct contains floating points in one
   member, but integers in another, the integers take precedence.

Bug: #38491

tools/build.py run_ffi_unit_tests && tools/test.py ffi_unit
TEST=runtime/vm/compiler/ffi/native_calling_convention_test.cc
TEST=runtime/vm/compiler/ffi/native_type_test.cc
These tests are exercised on vm-precomp-ffi-qemu-linux-release-arm-try.

Change-Id: Ib97769457d1ad0fce47601e9e4a3008bd837167f
Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194422
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
  • Loading branch information
dcharkes authored and commit-bot@chromium.org committed Apr 8, 2021
1 parent 2f327a6 commit 9d5846b
Show file tree
Hide file tree
Showing 199 changed files with 1,477 additions and 834 deletions.
128 changes: 107 additions & 21 deletions runtime/vm/compiler/ffi/native_calling_convention_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct3bytesx10) {
member_types.Add(&int8type);
member_types.Add(&int8type);
member_types.Add(&int8type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 10);
arguments.Add(&struct_type);
Expand Down Expand Up @@ -138,8 +137,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesHomogenousx10) {
member_types.Add(&float_type);
member_types.Add(&float_type);
member_types.Add(&float_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 13);
arguments.Add(&struct_type);
Expand Down Expand Up @@ -174,14 +172,13 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesHomogenousx10_2) {
auto& full_float_member_types = *new (Z) NativeTypes(Z, 1);
full_float_member_types.Add(&float_2_array_type);
const auto& float_array_struct_type =
NativeCompoundType::FromNativeTypes(Z, full_float_member_types);
NativeStructType::FromNativeTypes(Z, full_float_member_types);

auto& member_types = *new (Z) NativeTypes(Z, 3);
member_types.Add(&float_1_array_type);
member_types.Add(&float_array_struct_type);
member_types.Add(&float_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 13);
arguments.Add(&struct_type);
Expand All @@ -203,6 +200,55 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesHomogenousx10_2) {
RunSignatureTest(Z, "struct16bytesHomogenousx10", arguments, struct_type);
}

// Test with homogenous union.
//
// Even though the number of floats nested is different, this is still laid
// out as a homogeneous aggregate in arm64 and arm hardfp.
//
// Even though the member sizes are different, these unions are still passed in
// xmm registers on Linux/MacOS x64.
//
// See the *.expect in ./unit_tests for this behavior.
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_union16bytesHomogenousx10) {
const auto& float_type = *new (Z) NativePrimitiveType(kFloat);
const auto& int8type = *new (Z) NativePrimitiveType(kInt8);

const auto& float_array_type = *new (Z) NativeArrayType(float_type, 3);

auto& struct_member_types = *new (Z) NativeTypes(Z, 4);
struct_member_types.Add(&float_type);
struct_member_types.Add(&float_type);
struct_member_types.Add(&float_type);
struct_member_types.Add(&float_type);
const auto& struct_type =
NativeStructType::FromNativeTypes(Z, struct_member_types);

auto& member_types = *new (Z) NativeTypes(Z, 2);
member_types.Add(&float_array_type);
member_types.Add(&struct_type);
const auto& union_type = NativeUnionType::FromNativeTypes(Z, member_types);

EXPECT_EQ(16, union_type.SizeInBytes());
EXPECT(union_type.ContainsHomogenuousFloats());

auto& arguments = *new (Z) NativeTypes(Z, 13);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&int8type); // Check integer register back filling, if any.
arguments.Add(&union_type); // Check stack alignment of struct.

// Identical expectation files as previous test, struct contains the same
// members, but nested in arrays and nested structs.
RunSignatureTest(Z, "union16bytesHomogenousx10", arguments, union_type);
}

// A fairly big struct.
//
// On arm, split up in 8-byte chunks. The first chunk goes into two registers,
Expand Down Expand Up @@ -237,8 +283,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct128bytesx1) {
member_types.Add(&int64_type);
member_types.Add(&int64_type);
member_types.Add(&int64_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 2);
arguments.Add(&struct_type);
Expand All @@ -260,8 +305,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesMixedx10) {
member_types.Add(&float_type);
member_types.Add(&int32_type);
member_types.Add(&int32_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 11);
arguments.Add(&struct_type);
Expand Down Expand Up @@ -291,8 +335,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesMixedx10_2) {
member_types.Add(&float_type);
member_types.Add(&int32_type);
member_types.Add(&int32_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 15);
arguments.Add(&float_type);
Expand Down Expand Up @@ -342,20 +385,19 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct16bytesMixedx10_3) {
half_float_member_types.Add(&int32_type);
half_float_member_types.Add(&float_type);
const auto& half_float_type =
NativeCompoundType::FromNativeTypes(Z, half_float_member_types);
NativeStructType::FromNativeTypes(Z, half_float_member_types);

const auto& float_array_type = *new (Z) NativeArrayType(float_type, 1);
auto& full_float_member_types = *new (Z) NativeTypes(Z, 1);
full_float_member_types.Add(&float_array_type);
const auto& full_float_type =
NativeCompoundType::FromNativeTypes(Z, full_float_member_types);
NativeStructType::FromNativeTypes(Z, full_float_member_types);

auto& member_types = *new (Z) NativeTypes(Z, 3);
member_types.Add(&int32_type);
member_types.Add(&half_float_type);
member_types.Add(&full_float_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 11);
arguments.Add(&struct_type);
Expand Down Expand Up @@ -389,8 +431,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct8bytesx1) {
member_types.Add(&int8type);
member_types.Add(&int8type);
member_types.Add(&int8type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types);
const auto& struct_type = NativeStructType::FromNativeTypes(Z, member_types);

auto& arguments = *new (Z) NativeTypes(Z, 1);
arguments.Add(&struct_type);
Expand Down Expand Up @@ -418,7 +459,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct8bytesPackedx10) {
member_types.Add(&int8_type);
member_types.Add(&int8_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types, /*packing=*/1);
NativeStructType::FromNativeTypes(Z, member_types, /*packing=*/1);
EXPECT_EQ(8, struct_type.SizeInBytes());
EXPECT(struct_type.ContainsUnalignedMembers());

Expand Down Expand Up @@ -456,7 +497,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_structPacked) {
member_types.Add(&int8_type);
member_types.Add(&double_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types, /*packing=*/1);
NativeStructType::FromNativeTypes(Z, member_types, /*packing=*/1);
EXPECT_EQ(9, struct_type.SizeInBytes());
EXPECT(struct_type.ContainsUnalignedMembers());

Expand All @@ -469,6 +510,51 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_structPacked) {
RunSignatureTest(Z, "structPacked", arguments, struct_type);
}

// The union is only 5 bytes because it's members are packed.
//
// Many calling conventions pass this struct in single registers or less
// stack slots because of this.
//
// Non-windows x64 passes this struct on the stack instead of in a single
// CPU register, because it contains a mis-aligned member.
//
// See the *.expect in ./unit_tests for this behavior.
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_union5bytesPackedx10) {
const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
const auto& uint32_type = *new (Z) NativePrimitiveType(kUint32);

auto& inner_members = *new (Z) NativeTypes(Z, 2);
inner_members.Add(&uint8_type);
inner_members.Add(&uint32_type);
const intptr_t packing = 1;
const auto& struct_type =
NativeStructType::FromNativeTypes(Z, inner_members, packing);

const auto& array_type = *new (Z) NativeArrayType(uint8_type, 5);

auto& member_types = *new (Z) NativeTypes(Z, 2);
member_types.Add(&array_type);
member_types.Add(&struct_type);
const auto& union_type = NativeUnionType::FromNativeTypes(Z, member_types);

EXPECT_EQ(5, union_type.SizeInBytes());
EXPECT_EQ(1, union_type.AlignmentInBytesField());

auto& arguments = *new (Z) NativeTypes(Z, 10);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);
arguments.Add(&union_type);

RunSignatureTest(Z, "union5bytesPackedx10", arguments, union_type);
}

} // namespace ffi
} // namespace compiler
} // namespace dart
Loading

0 comments on commit 9d5846b

Please sign in to comment.