mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 09:58:32 +00:00
77d10f2ed9
Renames `ReturnInstr` to `DartReturnInstr`, and introduces a new `ReturnBaseInstr` to be the common parent of `DartReturnInstr` and `NativeReturnInstr`. (Before this CL, `NativeReturnInstr` was a subtype of `ReturnInstr`.) In a follow up CL, the `NativeReturnInstr` will get up to two inputs. https://dart-review.googlesource.com/c/sdk/+/354226 Therefore, the `ReturnBaseInstr` does not inherit from `TemplateInstr` with 1 input, but instead only inherits from `Instruction`. TEST=SDK build TEST=*_il_test.dart Change-Id: I017eb7802ae6c902b64f1cda20edf4a11408dbe1 Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358904 Reviewed-by: Tess Strickland <sstrickl@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
262 lines
6.8 KiB
Dart
262 lines
6.8 KiB
Dart
// 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',
|
|
),
|
|
],
|
|
];
|
|
}
|