Files
sdk/tests/ffi/array_compound_elements_test.dart
T
Michael Goderbauer c326065c3f [vm/ffi] Exposes an iterable Array.elements
For the arrays of the Int/Uint/Float/Double types this has been
implemented straight in the ffi_patch.dart file and `elements` returns a
typed-data list view on the original data backing the array.

The Bool and Pointer arrays return helper classes from their `elements`
getter. These helper classes implement the `List` interface utilizing
the `operator []` implementation of the underlying arrays. This is also
implemented directly in the ffi_patch.dart file.

The `elements` getter for array arrays is rewritten in the CFE
(use_sites.dart) to instantiate a helper class to which the size of a
single element of the array is passed (this information is not available
in ffi_patch.dart, hence the rewrite in the CFE). The helper class
implements the `List` interface and performs some pointer arithmetic
with the provided element size to implement its methods.

The `elements` getters for struct and union arrays are also rewritten in
the CFE to instantiate a helper class to which a constructor tearoff for
instantiating the underlying struct/union type is passed. The helper
class also implements the `List` interface and uses the provided
constructor tearoff to create instantiated structs/unions of the
elements in the list.

Last, but not least, the `elements` getter for abi-specific integer
arrays is also rewritten in the CFI to instantiate a helper class to
which a closure is provided to load abi specific integers from the
underlying array data structure.

TEST=tests/ffi/array_compound_elements_test.dart
TEST=tests/ffi/array_primitive_elements_generated_test.dart
CoreLibraryReviewExempt: Dart VM only.
Bug: https://github.com/dart-lang/sdk/issues/45508
Change-Id: I211a42174057c39632c6a363d4e8e6fa8e94e801
Cq-Include-Trybots: dart/try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-mac-debug-simarm64_arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-arm64-try,vm-fuchsia-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/414821
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
2025-03-20 06:38:27 -07:00

324 lines
8.7 KiB
Dart

// Copyright (c) 2025, 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.
//
// SharedObjects=ffi_test_functions
// VMOptions=
// VMOptions=--deterministic --optimization-counter-threshold=90
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
import 'dart:ffi';
import 'dart:typed_data';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
const int arrayLength = 5;
const int deeplyNestedArrayLength = 2;
void main() {
// Loop enough to trigger optimizations or stacktraces. See "VMOptions" above.
for (int i = 0; i < 100; ++i) {
testPointerArrayElements();
testMallocedPointerArrayElements();
testWriteToPointerArrayElements();
testStructArrayElements();
testMallocedStructArrayElements();
testWriteToStructArrayElements();
testUnionArrayElements();
testMallocedUnionArrayElements();
testWriteToUnionArrayElements();
testArrayArrayElements();
testMallocedArrayArrayElements();
testWriteToArrayArrayElements();
testDeeplyNestedArrayElements();
testMallocedDeeplyNestedArrayElements();
}
}
final class TestStruct extends Struct {
// Placeholder value before array to test the offset calculation logic.
@Int8()
external int placeholder;
@Array(arrayLength)
external Array<Pointer<Int8>> pointerArray;
@Array(arrayLength)
external Array<MyStruct> structArray;
@Array(arrayLength)
external Array<MyUnion> unionArray;
@Array(arrayLength, arrayLength)
external Array<Array<Int8>> arrayArray;
@Array(
deeplyNestedArrayLength,
deeplyNestedArrayLength,
deeplyNestedArrayLength,
deeplyNestedArrayLength,
)
external Array<Array<Array<Array<Int8>>>> deplyNestedArray;
}
final class MyStruct extends Struct {
@Int8()
external int structValue;
}
final class MyUnion extends Union {
@Int32()
external int unionAlt1;
@Float()
external double unionAlt2;
}
void testPointerArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.pointerArray;
final expected = <Pointer<Int8>>[];
for (int i = 0; i < arrayLength; i++) {
Pointer<Int8> intPointer = malloc<Int8>()..value = 100 + i;
array[i] = intPointer;
expected.add(intPointer);
}
Expect.listEquals(expected, array.elements);
expected.forEach(malloc.free);
}
void testMallocedPointerArrayElements() {
final struct = malloc<TestStruct>();
final array = struct.ref.pointerArray;
final expected = <Pointer<Int8>>[];
for (int i = 0; i < arrayLength; i++) {
Pointer<Int8> intPointer = malloc<Int8>()..value = 100 + i;
array[i] = intPointer;
expected.add(intPointer);
}
Expect.listEquals(expected, array.elements);
expected.forEach(malloc.free);
malloc.free(struct);
}
void testWriteToPointerArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.pointerArray;
final expected = <Pointer<Int8>>[];
for (int i = 0; i < arrayLength; i++) {
Pointer<Int8> intPointer = malloc<Int8>()..value = 100 + i;
array.elements[i] = intPointer;
expected.add(intPointer);
}
Expect.listEquals(expected, array.elements);
final actual = <Pointer<Int8>>[];
for (int i = 0; i < arrayLength; i++) {
actual.add(array[i]);
}
Expect.listEquals(expected, actual);
expected.forEach(malloc.free);
}
void testStructArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.structArray;
final expected = <int>[];
for (int i = 0; i < arrayLength; i++) {
int value = 100 + i;
array[i].structValue = value;
expected.add(value);
}
Expect.listEquals(expected, [
for (var element in array.elements) element.structValue,
]);
}
void testMallocedStructArrayElements() {
final struct = malloc<TestStruct>();
final array = struct.ref.structArray;
final expected = <int>[];
for (int i = 0; i < arrayLength; i++) {
int value = 100 + i;
array[i].structValue = value;
expected.add(value);
}
Expect.listEquals(expected, [
for (var element in array.elements) element.structValue,
]);
malloc.free(struct);
}
void testWriteToStructArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.structArray;
final myStruct = Struct.create<MyStruct>();
for (int i = 0; i < arrayLength; i++) {
final e = Expect.throwsUnsupportedError(() {
array.elements[i] = myStruct;
});
Expect.isTrue(e.message!.contains('Cannot modify an unmodifiable list'));
}
}
void testUnionArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.unionArray;
final expected = <int>[];
for (int i = 0; i < arrayLength; i++) {
int value = 100 + i;
array[i].unionAlt1 = value;
expected.add(value);
}
Expect.listEquals(expected, [
for (var element in array.elements) element.unionAlt1,
]);
}
void testMallocedUnionArrayElements() {
final struct = malloc<TestStruct>();
final array = struct.ref.unionArray;
final expected = <int>[];
for (int i = 0; i < arrayLength; i++) {
int value = 100 + i;
array[i].unionAlt1 = value;
expected.add(value);
}
Expect.listEquals(expected, [
for (var element in array.elements) element.unionAlt1,
]);
malloc.free(struct);
}
void testWriteToUnionArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.unionArray;
final myUnion = Union.create<MyUnion>();
for (int i = 0; i < arrayLength; i++) {
final e = Expect.throwsUnsupportedError(() {
array.elements[i] = myUnion;
});
Expect.isTrue(e.message!.contains('Cannot modify an unmodifiable list'));
}
}
void testArrayArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.arrayArray;
final expected = <List<int>>[];
for (int i = 0; i < arrayLength; i++) {
final values = <int>[];
for (int j = 0; j < arrayLength; j++) {
int value = (10 * i) + j;
array[i][j] = value;
values.add(value);
}
expected.add(values);
}
Expect.deepEquals(expected, [
for (var element in array.elements) element.elements,
]);
}
void testMallocedArrayArrayElements() {
final struct = malloc<TestStruct>();
final array = struct.ref.arrayArray;
final expected = <List<int>>[];
for (int i = 0; i < arrayLength; i++) {
final values = <int>[];
for (int j = 0; j < arrayLength; j++) {
int value = (10 * i) + j;
array[i][j] = value;
values.add(value);
}
expected.add(values);
}
Expect.deepEquals(expected, [
for (var element in array.elements) element.elements,
]);
malloc.free(struct);
}
void testWriteToArrayArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.arrayArray;
final source = Struct.create<TestStruct>().arrayArray;
for (int i = 0; i < arrayLength; i++) {
for (int j = 0; j < arrayLength; j++) {
source[i][j] = (10 * i) + j;
}
array.elements[i] = source[i];
}
for (int i = 0; i < arrayLength; i++) {
final actual = <int>[];
for (int j = 0; j < arrayLength; j++) {
actual.add(array[i][j]);
}
Expect.listEquals(source[i].elements, actual);
Expect.listEquals(source[i].elements, array[i].elements);
}
}
void testDeeplyNestedArrayElements() {
final struct = Struct.create<TestStruct>();
final array = struct.deplyNestedArray;
final expected = <int>[];
for (int a = 0; a < deeplyNestedArrayLength; a++) {
for (int b = 0; b < deeplyNestedArrayLength; b++) {
for (int c = 0; c < deeplyNestedArrayLength; c++) {
for (int d = 0; d < deeplyNestedArrayLength; d++) {
int value = a + b + c + d;
array[a][b][c][d] = value;
expected.add(value);
}
}
}
}
final actual = <int>[];
for (var a in array.elements) {
for (var b in a.elements) {
for (var c in b.elements) {
for (var d in c.elements) {
actual.add(d);
}
}
}
}
Expect.listEquals(expected, actual);
}
void testMallocedDeeplyNestedArrayElements() {
final struct = malloc<TestStruct>();
final array = struct.ref.deplyNestedArray;
final expected = <int>[];
for (int a = 0; a < deeplyNestedArrayLength; a++) {
for (int b = 0; b < deeplyNestedArrayLength; b++) {
for (int c = 0; c < deeplyNestedArrayLength; c++) {
for (int d = 0; d < deeplyNestedArrayLength; d++) {
int value = a + b + c + d;
array[a][b][c][d] = value;
expected.add(value);
}
}
}
}
final actual = <int>[];
for (var a in array.elements) {
for (var b in a.elements) {
for (var c in b.elements) {
for (var d in c.elements) {
actual.add(d);
}
}
}
}
Expect.listEquals(expected, actual);
malloc.free(struct);
}