Skip to content

Commit 295e210

Browse files
alexmarkovCommit Queue
authored and
Commit Queue
committed
[vm/aot] Unbox records using inferred record shapes
Unboxing of records in return values is switched to use inferred record shape instead of a static type. This is more accurate, as static return type 'Object', 'dynamic' or a type parameter would not prevent record unboxing. TEST=runtime/tests/vm/dart/records_return_value_unboxing_il_test.dart TEST=pkg/vm/testcases/transformations/type_flow/transformer/unboxed_records.dart Issue: #49719 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try Change-Id: I56528e114bc2de94c4a1ec09c48eb5b9ed3c3d3f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/288824 Commit-Queue: Alexander Markov <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 2d1f2ab commit 295e210

File tree

16 files changed

+408
-130
lines changed

16 files changed

+408
-130
lines changed

pkg/vm/lib/metadata/unboxing_info.dart

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,82 +3,139 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:kernel/ast.dart';
6+
import 'package:vm/transformations/type_flow/types.dart' show RecordShape;
67

7-
class UnboxingInfoMetadata {
8-
static const kBoxed = 0;
9-
static const kUnboxedIntCandidate = 1 << 0;
10-
static const kUnboxedDoubleCandidate = 1 << 1;
11-
static const kUnboxedRecordCandidate = 1 << 2;
12-
static const kUnboxingCandidate =
13-
kUnboxedIntCandidate | kUnboxedDoubleCandidate | kUnboxedRecordCandidate;
8+
enum UnboxingKind {
9+
boxed,
10+
int,
11+
double,
12+
record,
13+
unknown, // Not calculated yet.
14+
}
1415

15-
final List<int> unboxedArgsInfo;
16-
int returnInfo;
16+
class UnboxingType {
17+
final UnboxingKind kind;
18+
final RecordShape? recordShape;
1719

18-
UnboxingInfoMetadata(int argsLen)
19-
: unboxedArgsInfo = [],
20-
returnInfo = kUnboxingCandidate {
21-
for (int i = 0; i < argsLen; i++) {
22-
unboxedArgsInfo.add(kUnboxingCandidate);
20+
const UnboxingType._(this.kind, this.recordShape);
21+
UnboxingType.record(RecordShape shape) : this._(UnboxingKind.record, shape);
22+
23+
static const kUnknown = UnboxingType._(UnboxingKind.unknown, null);
24+
static const kInt = UnboxingType._(UnboxingKind.int, null);
25+
static const kDouble = UnboxingType._(UnboxingKind.double, null);
26+
static const kBoxed = UnboxingType._(UnboxingKind.boxed, null);
27+
28+
UnboxingType intersect(UnboxingType other) {
29+
if (kind == UnboxingKind.unknown) return other;
30+
if (other.kind == UnboxingKind.unknown) return this;
31+
if (this == other) return this;
32+
return kBoxed;
33+
}
34+
35+
@override
36+
bool operator ==(other) =>
37+
identical(this, other) ||
38+
(other is UnboxingType &&
39+
this.kind == other.kind &&
40+
this.recordShape == other.recordShape);
41+
42+
@override
43+
int get hashCode => (kind.index * 31) + recordShape.hashCode;
44+
45+
void writeToBinary(BinarySink sink) {
46+
sink.writeUInt30(kind.index);
47+
if (kind == UnboxingKind.record) {
48+
recordShape!.writeToBinary(sink);
49+
}
50+
}
51+
52+
factory UnboxingType.readFromBinary(BinarySource source) {
53+
final kind = UnboxingKind.values[source.readUInt30()];
54+
final recordShape = (kind == UnboxingKind.record)
55+
? RecordShape.readFromBinary(source)
56+
: null;
57+
return UnboxingType._(kind, recordShape);
58+
}
59+
60+
@override
61+
String toString() {
62+
switch (kind) {
63+
case UnboxingKind.boxed:
64+
return 'b';
65+
case UnboxingKind.int:
66+
return 'i';
67+
case UnboxingKind.double:
68+
return 'd';
69+
case UnboxingKind.record:
70+
{
71+
final sb = StringBuffer();
72+
sb.write('r<');
73+
sb.write(recordShape!.numPositionalFields.toString());
74+
for (final named in recordShape!.namedFields) {
75+
sb.write(',');
76+
sb.write(named);
77+
}
78+
sb.write('>');
79+
return sb.toString();
80+
}
81+
case UnboxingKind.unknown:
82+
throw 'Unexpected UnboxingType.kUknown';
2383
}
2484
}
85+
}
86+
87+
class UnboxingInfoMetadata {
88+
final List<UnboxingType> argsInfo;
89+
UnboxingType returnInfo;
90+
91+
UnboxingInfoMetadata(int argsLen)
92+
: argsInfo = List<UnboxingType>.filled(argsLen, UnboxingType.kUnknown,
93+
growable: true),
94+
returnInfo = UnboxingType.kUnknown;
2595

2696
UnboxingInfoMetadata.readFromBinary(BinarySource source)
27-
: unboxedArgsInfo = List<int>.generate(
28-
source.readUInt30(), (_) => source.readByte(),
97+
: argsInfo = List<UnboxingType>.generate(
98+
source.readUInt30(), (_) => UnboxingType.readFromBinary(source),
2999
growable: true),
30-
returnInfo = source.readByte();
100+
returnInfo = UnboxingType.readFromBinary(source);
31101

32102
// Returns `true` if all arguments as well as the return value have to be
33103
// boxed.
34104
//
35105
// We don't have to write out metadata for fully boxed methods, because this
36106
// is the default.
37107
bool get isFullyBoxed {
38-
if (returnInfo != kBoxed) return false;
39-
for (int argInfo in unboxedArgsInfo) {
40-
if (argInfo != kBoxed) return false;
108+
if (returnInfo != UnboxingType.kBoxed) return false;
109+
for (final argInfo in argsInfo) {
110+
if (argInfo != UnboxingType.kBoxed) return false;
41111
}
42112
return true;
43113
}
44114

45115
void writeToBinary(BinarySink sink) {
46-
sink.writeUInt30(unboxedArgsInfo.length);
47-
for (int val in unboxedArgsInfo) {
48-
sink.writeByte(val);
116+
sink.writeUInt30(argsInfo.length);
117+
for (final argInfo in argsInfo) {
118+
argInfo.writeToBinary(sink);
49119
}
50-
sink.writeByte(returnInfo);
120+
returnInfo.writeToBinary(sink);
51121
}
52122

53123
@override
54124
String toString() {
55125
final sb = StringBuffer();
56126
sb.write('(');
57-
for (int i = 0; i < unboxedArgsInfo.length; ++i) {
58-
final argInfo = unboxedArgsInfo[i];
59-
sb.write(_stringifyUnboxingInfo(argInfo));
60-
if (i != (unboxedArgsInfo.length - 1)) {
127+
for (int i = 0; i < argsInfo.length; ++i) {
128+
final argInfo = argsInfo[i];
129+
sb.write(argInfo.toString());
130+
if (i != (argsInfo.length - 1)) {
61131
sb.write(',');
62132
}
63133
}
64134
sb.write(')');
65135
sb.write('->');
66-
sb.write(_stringifyUnboxingInfo(returnInfo));
136+
sb.write(returnInfo.toString());
67137
return sb.toString();
68138
}
69-
70-
static String _stringifyUnboxingInfo(int info) {
71-
switch (info) {
72-
case UnboxingInfoMetadata.kUnboxedIntCandidate:
73-
return 'i';
74-
case UnboxingInfoMetadata.kUnboxedDoubleCandidate:
75-
return 'd';
76-
case UnboxingInfoMetadata.kUnboxedRecordCandidate:
77-
return 'r';
78-
}
79-
assert(info == UnboxingInfoMetadata.kBoxed);
80-
return 'b';
81-
}
82139
}
83140

84141
class UnboxingInfoMetadataRepository

pkg/vm/lib/transformations/type_flow/analysis.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,16 +1006,15 @@ class _TFClassImpl extends TFClass {
10061006
late final Map<Name, Member> _dispatchTargetsNonSetters =
10071007
_initDispatchTargets(false);
10081008
final _DependencyTracker dependencyTracker = new _DependencyTracker();
1009-
final RecordShape? recordShape;
10101009

10111010
/// Flag indicating if this class has a noSuchMethod() method not inherited
10121011
/// from Object.
10131012
/// Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod().
10141013
bool? hasNonTrivialNoSuchMethod;
10151014

10161015
_TFClassImpl(int id, Class classNode, this.superclass, this.supertypes,
1017-
this.recordShape)
1018-
: super(id, classNode) {
1016+
RecordShape? recordShape)
1017+
: super(id, classNode, recordShape) {
10191018
supertypes.add(this);
10201019
}
10211020

pkg/vm/lib/transformations/type_flow/rta.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class ClassInfo extends TFClass {
5555

5656
ClassInfo(int id, Class classNode, this.superclass, this.supertypes,
5757
this.calledDynamicSelectors, this.calledVirtualSelectors)
58-
: super(id, classNode) {
58+
: super(id, classNode, null) {
5959
supertypes.add(this);
6060
for (var sup in supertypes) {
6161
sup.subtypes.add(this);

pkg/vm/lib/transformations/type_flow/transformer.dart

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -519,17 +519,12 @@ class AnnotateKernel extends RecursiveVisitor {
519519
_unboxingInfo.getUnboxingInfoOfMember(member);
520520
if (unboxingInfoMetadata != null) {
521521
// Check for partitions that only have abstract methods should be marked as boxed.
522-
if (unboxingInfoMetadata.returnInfo ==
523-
UnboxingInfoMetadata.kUnboxingCandidate) {
524-
unboxingInfoMetadata.returnInfo = UnboxingInfoMetadata.kBoxed;
522+
if (unboxingInfoMetadata.returnInfo == UnboxingType.kUnknown) {
523+
unboxingInfoMetadata.returnInfo = UnboxingType.kBoxed;
525524
}
526-
for (int i = 0;
527-
i < unboxingInfoMetadata.unboxedArgsInfo.length;
528-
i++) {
529-
if (unboxingInfoMetadata.unboxedArgsInfo[i] ==
530-
UnboxingInfoMetadata.kUnboxingCandidate) {
531-
unboxingInfoMetadata.unboxedArgsInfo[i] =
532-
UnboxingInfoMetadata.kBoxed;
525+
for (int i = 0; i < unboxingInfoMetadata.argsInfo.length; i++) {
526+
if (unboxingInfoMetadata.argsInfo[i] == UnboxingType.kUnknown) {
527+
unboxingInfoMetadata.argsInfo[i] = UnboxingType.kBoxed;
533528
}
534529
}
535530
if (!unboxingInfoMetadata.isFullyBoxed) {

pkg/vm/lib/transformations/type_flow/types.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,19 @@ import 'utils.dart';
2424
class TFClass {
2525
final int id;
2626
final Class classNode;
27+
final RecordShape? recordShape;
2728

2829
/// TFClass should not be instantiated directly.
2930
/// Instead, [TypeHierarchy.getTFClass] should be used to obtain [TFClass]
3031
/// instances specific to given [TypeHierarchy].
31-
TFClass(this.id, this.classNode);
32+
TFClass(this.id, this.classNode, this.recordShape);
3233

3334
/// Returns ConcreteType corresponding to this class without
3435
/// any extra attributes.
3536
ConcreteType get concreteType => ConcreteType(this);
3637

38+
bool get isRecord => recordShape != null;
39+
3740
@override
3841
int get hashCode => id;
3942

@@ -54,7 +57,7 @@ class RecordShape {
5457
: numPositionalFields = type.positional.length,
5558
namedFields = type.named.isEmpty
5659
? const <String>[]
57-
: type.named.map((nt) => nt.name).toList();
60+
: type.named.map((nt) => nt.name).toList(growable: false);
5861

5962
int get numFields => numPositionalFields + namedFields.length;
6063

@@ -66,6 +69,20 @@ class RecordShape {
6669
}
6770
}
6871

72+
RecordShape.readFromBinary(BinarySource source)
73+
: numPositionalFields = source.readUInt30(),
74+
namedFields = List<String>.generate(
75+
source.readUInt30(), (_) => source.readStringReference(),
76+
growable: false);
77+
78+
void writeToBinary(BinarySink sink) {
79+
sink.writeUInt30(numPositionalFields);
80+
sink.writeUInt30(namedFields.length);
81+
for (int i = 0; i < namedFields.length; ++i) {
82+
sink.writeStringReference(namedFields[i]);
83+
}
84+
}
85+
6986
@override
7087
int get hashCode => (_hash == 0) ? _computeHashCode() : _hash;
7188

pkg/vm/lib/transformations/type_flow/unboxing_info.dart

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import '../../metadata/unboxing_info.dart';
1717
import 'utils.dart';
1818

1919
class UnboxingInfoManager {
20+
// Unbox records in return values if they have
21+
// exactly this number of fields.
22+
static const int numRecordFieldsForReturnValueUnboxing = 2;
23+
2024
final Map<Member, UnboxingInfoMetadata> _memberInfo = {};
2125

2226
final TypeHierarchy _typeHierarchy;
@@ -93,8 +97,8 @@ class UnboxingInfoManager {
9397
info = UnboxingInfoMetadata(paramCount);
9498
selectorIdToInfo[selectorId] = info;
9599
} else {
96-
if (paramCount < info.unboxedArgsInfo.length) {
97-
info.unboxedArgsInfo.length = paramCount;
100+
if (paramCount < info.argsInfo.length) {
101+
info.argsInfo.length = paramCount;
98102
}
99103
}
100104
} else {
@@ -121,8 +125,8 @@ class UnboxingInfoManager {
121125
if (typeFlowAnalysis.isMemberUsed(member)) {
122126
final UnboxingInfoMetadata unboxingInfo = _memberInfo[member]!;
123127
if (_cannotUnbox(member)) {
124-
unboxingInfo.unboxedArgsInfo.length = 0;
125-
unboxingInfo.returnInfo = UnboxingInfoMetadata.kBoxed;
128+
unboxingInfo.argsInfo.length = 0;
129+
unboxingInfo.returnInfo = UnboxingType.kBoxed;
126130
return;
127131
}
128132
if (member is Procedure || member is Constructor) {
@@ -150,52 +154,51 @@ class UnboxingInfoManager {
150154
final Type resultType = typeFlowAnalysis.getSummary(member).resultType;
151155
_applyToReturn(member, unboxingInfo, resultType);
152156
} else if (member is Field) {
153-
final fieldValue = typeFlowAnalysis.getFieldValue(member).value;
157+
final inferredType = typeFlowAnalysis.getFieldValue(member).value;
154158
if (member.hasSetter) {
155-
_applyToArg(member, unboxingInfo, 0, fieldValue);
159+
_applyToArg(member, unboxingInfo, 0, inferredType);
156160
}
157-
_applyToReturn(member, unboxingInfo, fieldValue);
161+
_applyToReturn(member, unboxingInfo, inferredType);
158162
} else {
159163
assert(false, "Unexpected member: $member");
160164
}
161165
}
162166
}
163167

164-
int _getUnboxingType(Member member, Type type, bool isReturn) {
168+
UnboxingType _getUnboxingType(Member member, Type type, bool isReturn) {
165169
if (type is! NullableType) {
166170
if (type.isSubtypeOf(_typeHierarchy, _coreTypes.intClass)) {
167-
return UnboxingInfoMetadata.kUnboxedIntCandidate;
171+
return UnboxingType.kInt;
168172
}
169173
if (type.isSubtypeOf(_typeHierarchy, _coreTypes.doubleClass)) {
170-
return UnboxingInfoMetadata.kUnboxedDoubleCandidate;
174+
return UnboxingType.kDouble;
171175
}
172176
if (isReturn) {
173-
final function = member.function;
174-
if (function != null) {
175-
final returnType = function.returnType;
176-
if (returnType is RecordType &&
177-
(returnType.positional.length + returnType.named.length) == 2) {
178-
return UnboxingInfoMetadata.kUnboxedRecordCandidate;
179-
}
177+
if (type is ConcreteType &&
178+
type.cls.isRecord &&
179+
type.cls.recordShape!.numFields ==
180+
numRecordFieldsForReturnValueUnboxing) {
181+
return UnboxingType.record(type.cls.recordShape!);
180182
}
181183
}
182184
}
183-
return UnboxingInfoMetadata.kBoxed;
185+
return UnboxingType.kBoxed;
184186
}
185187

186188
void _applyToArg(
187189
Member member, UnboxingInfoMetadata unboxingInfo, int argPos, Type type) {
188-
if (argPos < 0 || unboxingInfo.unboxedArgsInfo.length <= argPos) {
190+
if (argPos < 0 || unboxingInfo.argsInfo.length <= argPos) {
189191
return;
190192
}
191193
final unboxingType = _getUnboxingType(member, type, false);
192-
unboxingInfo.unboxedArgsInfo[argPos] &= unboxingType;
194+
unboxingInfo.argsInfo[argPos] =
195+
unboxingInfo.argsInfo[argPos].intersect(unboxingType);
193196
}
194197

195198
void _applyToReturn(
196199
Member member, UnboxingInfoMetadata unboxingInfo, Type type) {
197200
final unboxingType = _getUnboxingType(member, type, true);
198-
unboxingInfo.returnInfo &= unboxingType;
201+
unboxingInfo.returnInfo = unboxingInfo.returnInfo.intersect(unboxingType);
199202
}
200203

201204
bool _cannotUnbox(Member member) {

pkg/vm/test/transformations/type_flow/summary_collector_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class FakeTypesBuilder extends TypesBuilder {
3434

3535
@override
3636
TFClass getTFClass(Class c) =>
37-
_classes[c] ??= new TFClass(++_classIdCounter, c);
37+
_classes[c] ??= new TFClass(++_classIdCounter, c, null);
3838
}
3939

4040
class FakeEntryPointsListener implements EntryPointsListener {

0 commit comments

Comments
 (0)