Skip to content

Commit 9d1b447

Browse files
dcharkescommit-bot@chromium.org
authored andcommitted
[vm/ffi] Structs by value compiler frontend (part 1)
Split off https://dart-review.googlesource.com/c/sdk/+/140290 to make that CL smaller. This CL adds support for struct types in the ffi compiler frontend in `runtime/vm/compiler/ffi/native_type.h`. The code in this CL is unit tested: 1. From Dart source to `NativeCompoundType` are tested as VM test. TEST=runtime/vm/compiler/ffi/native_type_vm_test.cc 2. The size and alignments for structs are tested with expect files. TEST=runtime/vm/compiler/ffi/native_type_test.cc The code in this CL has been end-to-end tested in the CL it is split off from. And will be end-to-end tested when that CL also lands. Issue: #36730 Change-Id: Ia7134e30daf028fea3ed97319ac7f5d0dbcc710f Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try,vm-ffi-android-debug-arm64-try,vm-ffi-android-debug-arm-try,vm-kernel-nnbd-win-debug-x64-try,vm-kernel-mac-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/172648 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Clement Skau <[email protected]>
1 parent 4a3f121 commit 9d1b447

Some content is hidden

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

51 files changed

+1001
-26
lines changed

runtime/bin/ffi_test/ffi_test_functions.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,8 @@ DART_EXPORT int64_t* NullableInt64ElemAt1(int64_t* a) {
610610
}
611611

612612
// A struct designed to exercise all kinds of alignment rules.
613-
// Note that offset32A (System V ia32) aligns doubles on 4 bytes while offset32B
614-
// (Arm 32 bit and MSVC ia32) aligns on 8 bytes.
613+
// Note that offset32A (System V ia32, iOS arm) aligns doubles on 4 bytes while
614+
// offset32B (Arm 32 bit and MSVC ia32) aligns on 8 bytes.
615615
// TODO(37271): Support nested structs.
616616
// TODO(37470): Add uncommon primitive data types when we want to support them.
617617
struct VeryLargeStruct {

runtime/vm/compiler/compiler_sources.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ compiler_sources_tests = [
179179
"backend/typed_data_aot_test.cc",
180180
"backend/yield_position_test.cc",
181181
"cha_test.cc",
182+
"ffi/native_type_vm_test.cc",
182183
"frontend/kernel_binary_flowgraph_test.cc",
183184
"frontend/multiple_entrypoints_test.cc",
184185
"write_barrier_elimination_test.cc",

runtime/vm/compiler/ffi/native_location.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "platform/assert.h"
1313
#include "vm/compiler/ffi/native_type.h"
14+
#include "vm/compiler/runtime_api.h"
1415
#include "vm/constants.h"
1516
#include "vm/growable_array.h"
1617

@@ -24,10 +25,6 @@ class BaseTextBuffer;
2425

2526
namespace compiler {
2627

27-
namespace target {
28-
extern const int kWordSize;
29-
}
30-
3128
namespace ffi {
3229

3330
class NativeRegistersLocation;

runtime/vm/compiler/ffi/native_type.cc

Lines changed: 234 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include "vm/compiler/backend/locations.h"
1515
#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
1616

17+
#if !defined(FFI_UNIT_TESTS)
18+
#include "vm/symbols.h"
19+
#endif
20+
1721
namespace dart {
1822

1923
namespace compiler {
@@ -25,6 +29,11 @@ const NativePrimitiveType& NativeType::AsPrimitive() const {
2529
return static_cast<const NativePrimitiveType&>(*this);
2630
}
2731

32+
const NativeCompoundType& NativeType::AsCompound() const {
33+
ASSERT(IsCompound());
34+
return static_cast<const NativeCompoundType&>(*this);
35+
}
36+
2837
bool NativePrimitiveType::IsInt() const {
2938
switch (representation_) {
3039
case kInt8:
@@ -127,6 +136,57 @@ intptr_t NativePrimitiveType::AlignmentInBytesField() const {
127136
}
128137
}
129138

139+
static bool ContainsHomogenuousFloatsInternal(const NativeTypes& types);
140+
141+
// Keep consistent with
142+
// pkg/vm/lib/transformations/ffi_definitions.dart:_calculateSizeAndOffsets.
143+
NativeCompoundType& NativeCompoundType::FromNativeTypes(
144+
Zone* zone,
145+
const NativeTypes& members) {
146+
intptr_t offset = 0;
147+
148+
const intptr_t kAtLeast1ByteAligned = 1;
149+
// If this struct is nested in another struct, it should be aligned to the
150+
// largest alignment of its members.
151+
intptr_t alignment_field = kAtLeast1ByteAligned;
152+
// If this struct is passed on the stack, it should be aligned to the largest
153+
// alignment of its members when passing those members on the stack.
154+
intptr_t alignment_stack = kAtLeast1ByteAligned;
155+
#if defined(TARGET_OS_MACOS_IOS) && defined(TARGET_ARCH_ARM64)
156+
// On iOS64 stack values can be less aligned than wordSize, which deviates
157+
// from the arm64 ABI.
158+
ASSERT(CallingConventions::kArgumentStackAlignment == kAlignedToValueSize);
159+
// Because the arm64 ABI aligns primitives to word size on the stack, every
160+
// struct will be automatically aligned to word size. iOS64 does not align
161+
// the primitives to word size, so we set structs to align to word size for
162+
// iOS64.
163+
// However, homogenous structs are treated differently. They are aligned to
164+
// their member alignment. (Which is 4 in case of a homogenous float).
165+
// Source: manual testing.
166+
if (!ContainsHomogenuousFloatsInternal(members)) {
167+
alignment_stack = compiler::target::kWordSize;
168+
}
169+
#endif
170+
171+
auto& member_offsets =
172+
*new (zone) ZoneGrowableArray<intptr_t>(zone, members.length());
173+
for (intptr_t i = 0; i < members.length(); i++) {
174+
const NativeType& member = *members[i];
175+
const intptr_t member_size = member.SizeInBytes();
176+
const intptr_t member_align_field = member.AlignmentInBytesField();
177+
const intptr_t member_align_stack = member.AlignmentInBytesStack();
178+
offset = Utils::RoundUp(offset, member_align_field);
179+
member_offsets.Add(offset);
180+
offset += member_size;
181+
alignment_field = Utils::Maximum(alignment_field, member_align_field);
182+
alignment_stack = Utils::Maximum(alignment_stack, member_align_stack);
183+
}
184+
const intptr_t size = Utils::RoundUp(offset, alignment_field);
185+
186+
return *new (zone) NativeCompoundType(members, member_offsets, size,
187+
alignment_field, alignment_stack);
188+
}
189+
130190
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
131191
bool NativePrimitiveType::IsExpressibleAsRepresentation() const {
132192
switch (representation_) {
@@ -139,7 +199,7 @@ bool NativePrimitiveType::IsExpressibleAsRepresentation() const {
139199
case kInt32:
140200
case kUint32:
141201
case kInt64:
142-
case kUint64:
202+
case kUint64: // We don't actually have a kUnboxedUint64.
143203
case kFloat:
144204
case kDouble:
145205
return true;
@@ -179,6 +239,23 @@ bool NativePrimitiveType::Equals(const NativeType& other) const {
179239
return other.AsPrimitive().representation_ == representation_;
180240
}
181241

242+
bool NativeCompoundType::Equals(const NativeType& other) const {
243+
if (!other.IsCompound()) {
244+
return false;
245+
}
246+
const auto& other_compound = other.AsCompound();
247+
const auto& other_members = other_compound.members_;
248+
if (other_members.length() != members_.length()) {
249+
return false;
250+
}
251+
for (intptr_t i = 0; i < members_.length(); i++) {
252+
if (!members_[i]->Equals(*other_members[i])) {
253+
return false;
254+
}
255+
}
256+
return true;
257+
}
258+
182259
static PrimitiveType split_fundamental(PrimitiveType in) {
183260
switch (in) {
184261
case kInt16:
@@ -243,16 +320,52 @@ static PrimitiveType TypeRepresentation(classid_t class_id) {
243320
}
244321
}
245322

323+
static bool IsPredefinedFfiCid(classid_t class_id) {
324+
switch (class_id) {
325+
#define CASE_FFI_CID_TRUE(name) \
326+
case kFfi##name##Cid: \
327+
return true;
328+
CLASS_LIST_FFI(CASE_FFI_CID_TRUE)
329+
default:
330+
return false;
331+
}
332+
UNREACHABLE();
333+
}
334+
246335
NativeType& NativeType::FromTypedDataClassId(Zone* zone, classid_t class_id) {
247-
// TODO(36730): Support composites.
336+
ASSERT(IsPredefinedFfiCid(class_id));
248337
const auto fundamental_rep = TypeRepresentation(class_id);
249338
return *new (zone) NativePrimitiveType(fundamental_rep);
250339
}
251340

252341
#if !defined(FFI_UNIT_TESTS)
253342
NativeType& NativeType::FromAbstractType(Zone* zone, const AbstractType& type) {
254-
// TODO(36730): Support composites.
255-
return NativeType::FromTypedDataClassId(zone, type.type_class_id());
343+
const classid_t class_id = type.type_class_id();
344+
if (IsPredefinedFfiCid(class_id)) {
345+
return NativeType::FromTypedDataClassId(zone, class_id);
346+
}
347+
348+
// User-defined structs.
349+
const auto& cls = Class::Handle(zone, type.type_class());
350+
351+
auto& options = Object::Handle(zone);
352+
Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls,
353+
Symbols::vm_ffi_struct_fields(), &options);
354+
ASSERT(!options.IsNull());
355+
ASSERT(options.IsArray());
356+
357+
const auto& field_types = Array::Cast(options);
358+
auto& field_type = AbstractType::Handle(zone);
359+
auto& field_native_types = *new (zone) ZoneGrowableArray<const NativeType*>(
360+
zone, field_types.Length());
361+
for (intptr_t i = 0; i < field_types.Length(); i++) {
362+
field_type ^= field_types.At(i);
363+
const NativeType& field_native_type =
364+
NativeType::FromAbstractType(zone, field_type);
365+
field_native_types.Add(&field_native_type);
366+
}
367+
368+
return NativeCompoundType::FromNativeTypes(zone, field_native_types);
256369
}
257370
#endif
258371

@@ -281,15 +394,15 @@ NativePrimitiveType& NativeType::FromUnboxedRepresentation(Zone* zone,
281394
}
282395
#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
283396

284-
const char* NativeType::ToCString(Zone* zone) const {
397+
const char* NativeType::ToCString(Zone* zone, bool multi_line) const {
285398
ZoneTextBuffer textBuffer(zone);
286-
PrintTo(&textBuffer);
399+
PrintTo(&textBuffer, multi_line);
287400
return textBuffer.buffer();
288401
}
289402

290403
#if !defined(FFI_UNIT_TESTS)
291-
const char* NativeType::ToCString() const {
292-
return ToCString(Thread::Current()->zone());
404+
const char* NativeType::ToCString(bool multi_line) const {
405+
return ToCString(Thread::Current()->zone(), multi_line);
293406
}
294407
#endif
295408

@@ -324,11 +437,11 @@ static const char* PrimitiveTypeToCString(PrimitiveType rep) {
324437
}
325438
}
326439

327-
void NativeType::PrintTo(BaseTextBuffer* f) const {
440+
void NativeType::PrintTo(BaseTextBuffer* f, bool multi_line) const {
328441
f->AddString("I");
329442
}
330443

331-
void NativePrimitiveType::PrintTo(BaseTextBuffer* f) const {
444+
void NativePrimitiveType::PrintTo(BaseTextBuffer* f, bool multi_line) const {
332445
f->Printf("%s", PrimitiveTypeToCString(representation_));
333446
}
334447

@@ -338,6 +451,35 @@ const char* NativeFunctionType::ToCString(Zone* zone) const {
338451
return textBuffer.buffer();
339452
}
340453

454+
void NativeCompoundType::PrintTo(BaseTextBuffer* f, bool multi_line) const {
455+
f->AddString("Compound(");
456+
f->Printf("size: %" Pd ", ", SizeInBytes());
457+
f->Printf("field alignment: %" Pd ", ", AlignmentInBytesField());
458+
f->Printf("stack alignment: %" Pd ", ", AlignmentInBytesStack());
459+
f->AddString("members: {");
460+
if (multi_line) {
461+
f->AddString("\n ");
462+
}
463+
for (intptr_t i = 0; i < members_.length(); i++) {
464+
if (i > 0) {
465+
if (multi_line) {
466+
f->AddString(",\n ");
467+
} else {
468+
f->AddString(", ");
469+
}
470+
}
471+
f->Printf("%" Pd ": ", member_offsets_[i]);
472+
members_[i]->PrintTo(f);
473+
}
474+
if (multi_line) {
475+
f->AddString("\n");
476+
}
477+
f->AddString("})");
478+
if (multi_line) {
479+
f->AddString("\n");
480+
}
481+
}
482+
341483
#if !defined(FFI_UNIT_TESTS)
342484
const char* NativeFunctionType::ToCString() const {
343485
return ToCString(Thread::Current()->zone());
@@ -356,6 +498,88 @@ void NativeFunctionType::PrintTo(BaseTextBuffer* f) const {
356498
return_type_.PrintTo(f);
357499
}
358500

501+
bool NativeCompoundType::ContainsOnlyFloats(intptr_t offset_in_bytes,
502+
intptr_t size_in_bytes) const {
503+
ASSERT(size_in_bytes >= 0);
504+
const intptr_t first_byte = offset_in_bytes;
505+
const intptr_t last_byte = offset_in_bytes + size_in_bytes - 1;
506+
for (intptr_t i = 0; i < members_.length(); i++) {
507+
const intptr_t member_first_byte = member_offsets_[i];
508+
const intptr_t member_last_byte =
509+
member_first_byte + members_[i]->SizeInBytes() - 1;
510+
if ((first_byte <= member_first_byte && member_first_byte <= last_byte) ||
511+
(first_byte <= member_last_byte && member_last_byte <= last_byte)) {
512+
if (members_[i]->IsPrimitive() && !members_[i]->IsFloat()) {
513+
return false;
514+
}
515+
if (members_[i]->IsCompound()) {
516+
const auto& nested = members_[i]->AsCompound();
517+
const bool nested_only_floats = nested.ContainsOnlyFloats(
518+
offset_in_bytes - member_first_byte, size_in_bytes);
519+
if (!nested_only_floats) {
520+
return false;
521+
}
522+
}
523+
}
524+
if (member_first_byte > last_byte) {
525+
// None of the remaining members fits the range.
526+
break;
527+
}
528+
}
529+
return true;
530+
}
531+
532+
intptr_t NativeCompoundType::NumberOfWordSizeChunksOnlyFloat() const {
533+
// O(n^2) implementation, but only invoked for small structs.
534+
ASSERT(SizeInBytes() <= 16);
535+
const intptr_t size = SizeInBytes();
536+
intptr_t float_only_chunks = 0;
537+
for (intptr_t offset = 0; offset < size;
538+
offset += compiler::target::kWordSize) {
539+
if (ContainsOnlyFloats(
540+
offset, Utils::Minimum<intptr_t>(size - offset,
541+
compiler::target::kWordSize))) {
542+
float_only_chunks++;
543+
}
544+
}
545+
return float_only_chunks;
546+
}
547+
548+
intptr_t NativeCompoundType::NumberOfWordSizeChunksNotOnlyFloat() const {
549+
const intptr_t total_chunks =
550+
Utils::RoundUp(SizeInBytes(), compiler::target::kWordSize) /
551+
compiler::target::kWordSize;
552+
return total_chunks - NumberOfWordSizeChunksOnlyFloat();
553+
}
554+
555+
static void ContainsHomogenuousFloatsRecursive(const NativeTypes& types,
556+
bool* only_float,
557+
bool* only_double) {
558+
for (intptr_t i = 0; i < types.length(); i++) {
559+
const auto& member_type = types.At(i);
560+
if (member_type->IsPrimitive()) {
561+
PrimitiveType type = member_type->AsPrimitive().representation();
562+
*only_float = *only_float && (type == kFloat);
563+
*only_double = *only_double && (type == kDouble);
564+
}
565+
if (member_type->IsCompound()) {
566+
ContainsHomogenuousFloatsRecursive(member_type->AsCompound().members(),
567+
only_float, only_double);
568+
}
569+
}
570+
}
571+
572+
static bool ContainsHomogenuousFloatsInternal(const NativeTypes& types) {
573+
bool only_float = true;
574+
bool only_double = true;
575+
ContainsHomogenuousFloatsRecursive(types, &only_float, &only_double);
576+
return (only_double || only_float) && types.length() > 0;
577+
}
578+
579+
bool NativeCompoundType::ContainsHomogenuousFloats() const {
580+
return ContainsHomogenuousFloatsInternal(this->members());
581+
}
582+
359583
const NativeType& NativeType::WidenTo4Bytes(Zone* zone) const {
360584
if (IsInt() && SizeInBytes() <= 2) {
361585
if (IsSigned()) {

0 commit comments

Comments
 (0)