blob: accae0787f6da4f060ea3999ec637df803d411a4 [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.
// Check that `Pointer` are not allocated before being passed into a load.
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'package:vm/testing/il_matchers.dart';
void main() async {
using((arena) {
const length = 100;
final pointer = arena<Int8>(100);
for (int i = 0; i < length; i++) {
pointer[i] = i;
}
Expect.equals(
10,
testOffset(pointer),
);
Expect.equals(
10,
testAllocate(pointer),
);
Expect.equals(
45,
testHoist(pointer),
);
print(globalVar);
});
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int testOffset(Pointer<Int8> pointer) {
// `pointer2` is not allocated.
final pointer2 = pointer + 10;
return pointer2.value;
}
void matchIL$testOffset(FlowGraph graph) {
graph.dump();
graph.match([
match.block('Graph', [
'int 10' << match.UnboxedConstant(value: 10),
'int 0' << match.UnboxedConstant(value: 0),
]),
match.block('Function', [
'pointer' <<
match.Parameter(
index: 0,
),
'pointer.address untagged' <<
match.LoadField(
'pointer',
slot: 'PointerBase.data',
),
...convertUntaggedAddressToInt64('pointer'),
'pointer2.address int64' <<
match.BinaryInt64Op(
'pointer.address int64',
'int 10',
),
// `pointer2` is not allocated.
...convertInt64AddressToUntagged('pointer2'),
...loadIndexedValueAsInt64('pointer2', 'int 0'),
match.DartReturn('pointer2.value int64'),
]),
]);
}
class A {
final int i;
A(this.i);
}
A? globalVar;
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int testAllocate(Pointer<Int8> pointer) {
final pointer2 = pointer + 10;
globalVar = A(10);
return pointer2.value;
}
void matchIL$testAllocate(FlowGraph graph) {
graph.dump();
graph.match([
match.block('Graph', [
'int 10' << match.UnboxedConstant(value: 10),
'int 0' << match.UnboxedConstant(value: 0),
]),
match.block('Function', [
'pointer' <<
match.Parameter(
index: 0,
),
'pointer.address untagged' <<
match.LoadField(
'pointer',
slot: 'PointerBase.data',
),
...convertUntaggedAddressToInt64('pointer'),
'pointer2.address int64' <<
match.BinaryInt64Op(
'pointer.address int64',
'int 10',
),
...convertInt64AddressToUntagged('pointer2'),
// The untagged pointer2.address can live through an allocation
// even though it is marked `InnerPointerAccess::kMayBeInnerPointer`
// because its cid is a Pointer cid.
match.AllocateObject(),
match.StoreStaticField(match.any),
...loadIndexedValueAsInt64('pointer2', 'int 0'),
match.DartReturn('pointer2.value int64'),
]),
]);
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int testHoist(Pointer<Int8> pointer) {
int result = 0;
for (int i = 0; i < 10; i++) {
globalVar = A(10);
// The address load is hoisted out of the loop.
// The indexed load is _not_ hoisted out of the loop.
result += pointer[i];
}
return result;
}
void matchIL$testHoist(FlowGraph graph) {
graph.dump();
final indexRep = is32BitConfiguration ? 'int32' : 'int64';
graph.match([
match.block('Graph', [
'int 0' << match.UnboxedConstant(value: 0),
'int 10' << match.UnboxedConstant(value: 10),
'int 1' << match.UnboxedConstant(value: 1),
]),
match.block('Function', [
'pointer' <<
match.Parameter(
index: 0,
),
'pointer[i].address untagged' <<
match.LoadField(
'pointer',
slot: 'PointerBase.data',
),
match.Goto('B1'),
]),
'B1' <<
match.block('Join', [
'result int64' << match.Phi('int 0', 'result'),
'i int64' << match.Phi('int 0', 'i'),
match.CheckStackOverflow(),
match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
ifTrue: 'B2', ifFalse: 'B3'),
]),
'B2' <<
match.block('Target', [
// Do some allocation.
match.AllocateObject(),
match.StoreStaticField(match.any),
if (is32BitConfiguration) ...[
'i $indexRep' <<
match.IntConverter(
'i int64',
from: 'int64',
to: indexRep,
),
],
// Do a load indexed with the untagged pointer.address that is
// hoisted out of the loop.
...loadIndexedValueAsInt64('pointer[i]', 'i $indexRep'),
'result' <<
match.BinaryInt64Op(
match.any,
'pointer[i].value int64',
),
'i' <<
match.BinaryInt64Op(
match.any,
match.any,
),
match.Goto('B1'),
]),
'B3' <<
match.block('Target', [
match.DartReturn(match.any),
]),
]);
}
final addressRep = is32BitConfiguration ? 'uint32' : 'int64';
final valueRep = is32BitConfiguration ? 'int32' : 'int64';
List<Matcher> convertUntaggedAddressToInt64(String name) {
return [
'$name.address $addressRep' <<
match.IntConverter(
'$name.address untagged',
from: 'untagged',
to: addressRep,
),
if (is32BitConfiguration) ...[
'$name.address int64' <<
match.IntConverter(
'$name.address $addressRep',
from: addressRep,
to: 'int64',
),
],
];
}
List<Matcher> convertInt64AddressToUntagged(String name) {
return [
if (is32BitConfiguration) ...[
'$name.address $addressRep' <<
match.IntConverter(
'$name.address int64',
from: 'int64',
to: addressRep,
),
],
// `pointer2` is not allocated.
'$name.address untagged' <<
match.IntConverter(
'$name.address $addressRep',
from: addressRep,
to: 'untagged',
),
];
}
List<Matcher> loadIndexedValueAsInt64(String name, String index) {
return [
'$name.value $valueRep' <<
match.LoadIndexed(
'$name.address untagged',
index,
),
if (is32BitConfiguration) ...[
'$name.value int64' <<
match.IntConverter(
'$name.value $valueRep',
from: valueRep,
to: 'int64',
),
],
];
}