blob: 9af9addc2173d65adb09a9d0aefdfce6b9841cc3 [file] [log] [blame]
// Copyright (c) 2024, 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.
// Verifies that dart:ffi Struct is erased whenever possible and all
// of accessors are inlined.
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:vm/testing/il_matchers.dart';
final class S extends Struct {
@Array<Uint8>(8)
external Array<Uint8> magic;
@Int64()
external int f;
@Int64()
external int g;
external InnerS inner;
}
final class InnerS extends Struct {
@Int64()
external int x;
@Int64()
external int y;
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
void testAccessors1(S a, S b) {
for (var i = 0; i < 8; i++) {
a.magic[i] = b.magic[i];
}
a.f = b.inner.x;
a.g = b.inner.y;
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
void testAccessors2(Pointer<S> arrA, Pointer<S> arrB, int n) {
for (var j = 0; j < n; j++) {
final a = arrA[j];
final b = arrB[j];
for (var i = 0; i < 8; i++) {
a.magic[i] = b.magic[i];
}
a.f = b.inner.x;
a.g = b.inner.y;
}
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
S testCreation() {
return Struct.create<S>();
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
S testCreation2(Uint8List list) {
return Struct.create<S>(list);
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
void testCreationAndUse(Uint8List list) {
final s = Struct.create<S>(list);
for (var i = 0; i < 8; i++) {
s.magic[i] = i;
}
s.f = 1;
s.g = 2;
s.inner.x = 3;
s.inner.y = 4;
}
void main() {
final a = Struct.create<S>();
final b = Struct.create<S>();
testAccessors1(a, b);
final arrN = 2;
final arrA = calloc<S>(arrN);
final arrB = calloc<S>(arrN);
testAccessors2(arrA, arrB, arrN);
calloc.free(arrA);
calloc.free(arrB);
testCreation();
testCreation2(Uint8List(sizeOf<S>()));
testCreationAndUse(Uint8List(sizeOf<S>()));
}
void _checkNoCallsInGraph(FlowGraph graph) {
for (var block in graph.blocks()) {
for (var instr in block['is']) {
final op = instr['o'] as String;
if (op.endsWith('Call') && instr['d'].first != 'new RangeError.range') {
throw 'Found unexpected call: $instr';
}
}
}
}
void _checkNoAllocationInGraph(FlowGraph graph) {
for (var block in graph.blocks()) {
for (var instr in block['is']) {
final op = instr['o'] as String;
if (op == 'AllocateObject') {
if (instr['T']['c'] != 'RangeError') {
throw 'Found unexpected object allocation';
}
}
}
}
}
void matchIL$testAccessors1(FlowGraph graph) {
graph.dump();
_checkNoCallsInGraph(graph);
_checkNoAllocationInGraph(graph);
}
void matchIL$testAccessors2(FlowGraph graph) {
graph.dump();
_checkNoCallsInGraph(graph);
_checkNoAllocationInGraph(graph);
}
void matchIL$testCreation(FlowGraph graph) {
graph.dump();
_checkNoCallsInGraph(graph);
graph.match([
match.block('Graph', []),
match.block('Function', [
'S' << match.AllocateObject(),
'td' << match.AllocateTypedData(),
match.StoreField('S', 'td'),
match.DartReturn('S'),
]),
]);
}
void matchIL$testCreation2(FlowGraph graph) {
graph.dump();
_checkNoCallsInGraph(graph);
graph.match([
match.block('Graph', []),
match.block('Function', [
'td' << match.Parameter(index: 0),
'S' << match.AllocateObject(),
match.StoreField('S', 'td'),
]),
match.block('Target', [
'error' << match.AllocateObject(),
match.Throw('error'),
]),
match.block('Target', [
match.DartReturn('S'),
]),
]);
}
void matchIL$testCreationAndUse(FlowGraph graph) {
graph.dump();
_checkNoCallsInGraph(graph);
_checkNoAllocationInGraph(graph);
}