Skip to content

Commit

Permalink
[vm/ffi] Support packed Structs backend
Browse files Browse the repository at this point in the history
With packed structs, the x64 non-Windows ABI only put structs in CPU/FPU
registers when all it fields happen to be aligned.

This CL introduces the `NativeType::ContainsUnalignedMembers` query
for calling convention calculations.

Bug: #38158

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: I504f67ae22a6af375d5eb57ff26b58d340099df8
Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/186142
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Clement Skau <cskau@google.com>
  • Loading branch information
dcharkes authored and commit-bot@chromium.org committed Feb 22, 2021
1 parent 9a74bce commit bad5cc2
Show file tree
Hide file tree
Showing 33 changed files with 390 additions and 9 deletions.
4 changes: 2 additions & 2 deletions runtime/vm/compiler/ffi/native_calling_convention.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class ArgumentAllocator : public ValueObject {
const NativeLocation& AllocateCompound(
const NativeCompoundType& payload_type) {
const intptr_t size = payload_type.SizeInBytes();
if (size <= 16 && size > 0) {
if (size <= 16 && size > 0 && !payload_type.ContainsUnalignedMembers()) {
intptr_t required_regs =
payload_type.NumberOfWordSizeChunksNotOnlyFloat();
intptr_t required_xmm_regs =
Expand Down Expand Up @@ -516,7 +516,7 @@ static const NativeLocation& CompoundResultLocation(
Zone* zone,
const NativeCompoundType& payload_type) {
const intptr_t size = payload_type.SizeInBytes();
if (size <= 16 && size > 0) {
if (size <= 16 && size > 0 && !payload_type.ContainsUnalignedMembers()) {
// Allocate the same as argument, but use return registers instead of
// argument registers.
NativeLocations& multiple_locations =
Expand Down
71 changes: 71 additions & 0 deletions runtime/vm/compiler/ffi/native_calling_convention_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,77 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_struct8bytesx1) {
RunSignatureTest(Z, "struct8bytesx1", arguments, struct_type);
}

// The struct is only 8 bytes with packing enabled.
//
// 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_struct8bytesPackedx10) {
const auto& int8_type = *new (Z) NativePrimitiveType(kInt8);
const auto& int32_type = *new (Z) NativePrimitiveType(kInt32);

auto& member_types = *new (Z) NativeTypes(Z, 5);
member_types.Add(&int8_type);
member_types.Add(&int32_type);
member_types.Add(&int8_type);
member_types.Add(&int8_type);
member_types.Add(&int8_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types, /*packing=*/1);
EXPECT_EQ(8, struct_type.SizeInBytes());
EXPECT(struct_type.ContainsUnalignedMembers());

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

RunSignatureTest(Z, "struct8bytesPackedx10", arguments, struct_type);
}

// Without packing, this would be a 16 byte struct. However, because of packing
// it's 9 bytes.
//
// #pragma pack(push,1)
// typedef struct {
// int8_t a0;
// double a1
// } StructPacked;
// #pragma pack(pop)
//
// See the *.expect in ./unit_tests for this behavior.
UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_structPacked) {
const auto& int8_type = *new (Z) NativePrimitiveType(kInt8);
const auto& double_type = *new (Z) NativePrimitiveType(kDouble);

auto& member_types = *new (Z) NativeTypes(Z, 2);
member_types.Add(&int8_type);
member_types.Add(&double_type);
const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, member_types, /*packing=*/1);
EXPECT_EQ(9, struct_type.SizeInBytes());
EXPECT(struct_type.ContainsUnalignedMembers());

auto& arguments = *new (Z) NativeTypes(Z, 11);
arguments.Add(&struct_type);
arguments.Add(&struct_type);
arguments.Add(&int8_type); // Backfilling int registers.
arguments.Add(&double_type); // Backfilling float registers.

RunSignatureTest(Z, "structPacked", arguments, struct_type);
}

} // namespace ffi
} // namespace compiler
} // namespace dart
35 changes: 32 additions & 3 deletions runtime/vm/compiler/ffi/native_type.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ static bool ContainsHomogenuousFloatsInternal(const NativeTypes& types);
// pkg/vm/lib/transformations/ffi_definitions.dart:_calculateStructLayout.
NativeCompoundType& NativeCompoundType::FromNativeTypes(
Zone* zone,
const NativeTypes& members) {
const NativeTypes& members,
intptr_t member_packing) {
intptr_t offset = 0;

const intptr_t kAtLeast1ByteAligned = 1;
Expand Down Expand Up @@ -195,8 +196,13 @@ NativeCompoundType& NativeCompoundType::FromNativeTypes(
for (intptr_t i = 0; i < members.length(); i++) {
const NativeType& member = *members[i];
const intptr_t member_size = member.SizeInBytes();
const intptr_t member_align_field = member.AlignmentInBytesField();
const intptr_t member_align_stack = member.AlignmentInBytesStack();
const intptr_t member_align_field =
Utils::Minimum(member.AlignmentInBytesField(), member_packing);
intptr_t member_align_stack = member.AlignmentInBytesStack();
if (member_align_stack > member_packing &&
member_packing < compiler::target::kWordSize) {
member_align_stack = compiler::target::kWordSize;
}
offset = Utils::RoundUp(offset, member_align_field);
member_offsets.Add(offset);
offset += member_size;
Expand Down Expand Up @@ -710,6 +716,29 @@ intptr_t NativeCompoundType::NumberOfWordSizeChunksNotOnlyFloat() const {
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)

bool NativePrimitiveType::ContainsUnalignedMembers() const {
return false;
}

bool NativeArrayType::ContainsUnalignedMembers() const {
return element_type_.ContainsUnalignedMembers();
}

bool NativeCompoundType::ContainsUnalignedMembers() const {
for (intptr_t i = 0; i < members_.length(); i++) {
const auto& member = *members_.At(i);
const intptr_t member_offset = member_offsets_.At(i);
const intptr_t member_alignment = member.AlignmentInBytesField();
if (member_offset % member_alignment != 0) {
return true;
}
if (member.ContainsUnalignedMembers()) {
return true;
}
}
return false;
}

static void ContainsHomogenuousFloatsRecursive(const NativeTypes& types,
bool* only_float,
bool* only_double) {
Expand Down
15 changes: 13 additions & 2 deletions runtime/vm/compiler/ffi/native_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ class NativeType : public ZoneAllocated {
virtual bool ContainsOnlyFloats(Range range) const = 0;
#endif // !defined(DART_PRECOMPILED_RUNTIME)

// True iff any members are misaligned recursively due to packing.
virtual bool ContainsUnalignedMembers() const = 0;

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
// NativeTypes which are available as unboxed Representations.
virtual bool IsExpressibleAsRepresentation() const { return false; }
Expand Down Expand Up @@ -185,6 +188,8 @@ class NativePrimitiveType : public NativeType {
virtual bool ContainsOnlyFloats(Range range) const;
#endif // !defined(DART_PRECOMPILED_RUNTIME)

virtual bool ContainsUnalignedMembers() const;

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
virtual bool IsExpressibleAsRepresentation() const;
virtual Representation AsRepresentation() const;
Expand Down Expand Up @@ -234,6 +239,8 @@ class NativeArrayType : public NativeType {
virtual bool ContainsOnlyFloats(Range range) const;
#endif // !defined(DART_PRECOMPILED_RUNTIME)

virtual bool ContainsUnalignedMembers() const;

virtual bool Equals(const NativeType& other) const;

virtual void PrintTo(BaseTextBuffer* f,
Expand All @@ -255,8 +262,10 @@ using NativeTypes = ZoneGrowableArray<const NativeType*>;
// TODO(dartbug.com/38491): Support unions.
class NativeCompoundType : public NativeType {
public:
static NativeCompoundType& FromNativeTypes(Zone* zone,
const NativeTypes& members);
static NativeCompoundType& FromNativeTypes(
Zone* zone,
const NativeTypes& members,
intptr_t member_packing = kMaxInt32);

const NativeTypes& members() const { return members_; }
const ZoneGrowableArray<intptr_t>& member_offsets() const {
Expand Down Expand Up @@ -289,6 +298,8 @@ class NativeCompoundType : public NativeType {
intptr_t NumberOfWordSizeChunksNotOnlyFloat() const;
#endif // !defined(DART_PRECOMPILED_RUNTIME)

virtual bool ContainsUnalignedMembers() const;

// Whether this type has only same-size floating point members.
//
// Useful for determining whether struct is passed in FP registers in hardfp
Expand Down
22 changes: 20 additions & 2 deletions runtime/vm/compiler/ffi/native_type_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ namespace ffi {

const NativeCompoundType& RunStructTest(dart::Zone* zone,
const char* name,
const NativeTypes& member_types) {
const NativeTypes& member_types,
intptr_t packing = kMaxInt32) {
const auto& struct_type =
NativeCompoundType::FromNativeTypes(zone, member_types);
NativeCompoundType::FromNativeTypes(zone, member_types, packing);

const char* test_result = struct_type.ToCString(zone, /*multi_line=*/true);

Expand Down Expand Up @@ -196,6 +197,23 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_floatarray) {
struct_type.NumberOfWordSizeChunksOnlyFloat());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_packed) {
const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
const auto& uint16_type = *new (Z) NativePrimitiveType(kUint16);

auto& members = *new (Z) NativeTypes(Z, 2);
members.Add(&uint8_type);
members.Add(&uint16_type);
const intptr_t packing = 1;

const auto& struct_type =
NativeCompoundType::FromNativeTypes(Z, members, packing);

// Should be 3 bytes on every platform.
EXPECT_EQ(3, struct_type.SizeInBytes());
EXPECT(struct_type.ContainsUnalignedMembers());
}

} // namespace ffi
} // namespace compiler
} // namespace dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r0 int64) Compound(size: 8)
M(r1 int64) Compound(size: 8)
M(r2 int64) Compound(size: 8)
M(r3 int64) Compound(size: 8)
M(r4 int64) Compound(size: 8)
M(r5 int64) Compound(size: 8)
M(r6 int64) Compound(size: 8)
M(r7 int64) Compound(size: 8)
S+0 Compound(size: 8)
S+8 Compound(size: 8)
=>
M(r0 int64) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r0 int64) Compound(size: 8)
M(r1 int64) Compound(size: 8)
M(r2 int64) Compound(size: 8)
M(r3 int64) Compound(size: 8)
M(r4 int64) Compound(size: 8)
M(r5 int64) Compound(size: 8)
M(r6 int64) Compound(size: 8)
M(r7 int64) Compound(size: 8)
S+0 Compound(size: 8)
S+8 Compound(size: 8)
=>
M(r0 int64) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r0 int64) Compound(size: 8)
M(r1 int64) Compound(size: 8)
M(r2 int64) Compound(size: 8)
M(r3 int64) Compound(size: 8)
M(r4 int64) Compound(size: 8)
M(r5 int64) Compound(size: 8)
M(r6 int64) Compound(size: 8)
M(r7 int64) Compound(size: 8)
S+0 Compound(size: 8)
S+8 Compound(size: 8)
=>
M(r0 int64) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r0 int64) Compound(size: 8)
M(r1 int64) Compound(size: 8)
M(r2 int64) Compound(size: 8)
M(r3 int64) Compound(size: 8)
M(r4 int64) Compound(size: 8)
M(r5 int64) Compound(size: 8)
M(r6 int64) Compound(size: 8)
M(r7 int64) Compound(size: 8)
S+0 Compound(size: 8)
S+8 Compound(size: 8)
=>
M(r0 int64) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r1 int32, r2 int32) Compound(size: 8)
M(r3 int32, S+0 int32) Compound(size: 8)
M(S+4 int32, S+8 int32) Compound(size: 8)
M(S+12 int32, S+16 int32) Compound(size: 8)
M(S+20 int32, S+24 int32) Compound(size: 8)
M(S+28 int32, S+32 int32) Compound(size: 8)
M(S+36 int32, S+40 int32) Compound(size: 8)
M(S+44 int32, S+48 int32) Compound(size: 8)
M(S+52 int32, S+56 int32) Compound(size: 8)
M(S+60 int32, S+64 int32) Compound(size: 8)
=>
P(r0 uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r1 int32, r2 int32) Compound(size: 8)
M(r3 int32, S+0 int32) Compound(size: 8)
M(S+4 int32, S+8 int32) Compound(size: 8)
M(S+12 int32, S+16 int32) Compound(size: 8)
M(S+20 int32, S+24 int32) Compound(size: 8)
M(S+28 int32, S+32 int32) Compound(size: 8)
M(S+36 int32, S+40 int32) Compound(size: 8)
M(S+44 int32, S+48 int32) Compound(size: 8)
M(S+52 int32, S+56 int32) Compound(size: 8)
M(S+60 int32, S+64 int32) Compound(size: 8)
=>
P(r0 uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
M(r1 int32, r2 int32) Compound(size: 8)
M(r3 int32, S+0 int32) Compound(size: 8)
M(S+4 int32, S+8 int32) Compound(size: 8)
M(S+12 int32, S+16 int32) Compound(size: 8)
M(S+20 int32, S+24 int32) Compound(size: 8)
M(S+28 int32, S+32 int32) Compound(size: 8)
M(S+36 int32, S+40 int32) Compound(size: 8)
M(S+44 int32, S+48 int32) Compound(size: 8)
M(S+52 int32, S+56 int32) Compound(size: 8)
M(S+60 int32, S+64 int32) Compound(size: 8)
=>
P(r0 uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
S+4 Compound(size: 8)
S+12 Compound(size: 8)
S+20 Compound(size: 8)
S+28 Compound(size: 8)
S+36 Compound(size: 8)
S+44 Compound(size: 8)
S+52 Compound(size: 8)
S+60 Compound(size: 8)
S+68 Compound(size: 8)
S+76 Compound(size: 8)
=>
P(S+0 uint32, ret:eax uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
S+4 Compound(size: 8)
S+12 Compound(size: 8)
S+20 Compound(size: 8)
S+28 Compound(size: 8)
S+36 Compound(size: 8)
S+44 Compound(size: 8)
S+52 Compound(size: 8)
S+60 Compound(size: 8)
S+68 Compound(size: 8)
S+76 Compound(size: 8)
=>
P(S+0 uint32, ret:eax uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
S+0 Compound(size: 8)
S+8 Compound(size: 8)
S+16 Compound(size: 8)
S+24 Compound(size: 8)
S+32 Compound(size: 8)
S+40 Compound(size: 8)
S+48 Compound(size: 8)
S+56 Compound(size: 8)
S+64 Compound(size: 8)
S+72 Compound(size: 8)
=>
M(eax uint32, edx uint32) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
S+0 Compound(size: 8)
S+8 Compound(size: 8)
S+16 Compound(size: 8)
S+24 Compound(size: 8)
S+32 Compound(size: 8)
S+40 Compound(size: 8)
S+48 Compound(size: 8)
S+56 Compound(size: 8)
S+64 Compound(size: 8)
S+72 Compound(size: 8)
=>
P(rdi int64, ret:rax int64) Compound(size: 8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
S+0 Compound(size: 8)
S+8 Compound(size: 8)
S+16 Compound(size: 8)
S+24 Compound(size: 8)
S+32 Compound(size: 8)
S+40 Compound(size: 8)
S+48 Compound(size: 8)
S+56 Compound(size: 8)
S+64 Compound(size: 8)
S+72 Compound(size: 8)
=>
P(rdi int64, ret:rax int64) Compound(size: 8)
Loading

0 comments on commit bad5cc2

Please sign in to comment.