[modular_aot] Records

TEST=ci
Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: Ie6ba4148ca9193b5b28de3d90efa7bb55427ef53
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498660
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov
2026-05-18 08:03:32 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 5ed538a532
commit ccbcd76265
25 changed files with 662 additions and 10 deletions
+34
View File
@@ -2088,6 +2088,40 @@ class AstToIr extends ast.RecursiveVisitor {
throw 'Unexpected YieldStatement in $function with ${function.asyncMarker}';
}
}
@override
void visitRecordIndexGet(ast.RecordIndexGet node) {
final shape = RecordType(node.receiverType).shape;
_translateNode(node.receiver);
// TODO: canonicalize record fields
builder.addLoadInstanceField(CField(RecordField(shape, node.index)));
}
@override
void visitRecordNameGet(ast.RecordNameGet node) {
final shape = RecordType(node.receiverType).shape;
final namedIndex = shape.named.indexOf(node.name);
assert(namedIndex >= 0);
_translateNode(node.receiver);
// TODO: canonicalize record fields
builder.addLoadInstanceField(
CField(RecordField(shape, shape.positional + namedIndex)),
);
}
@override
void visitRecordLiteral(ast.RecordLiteral node) {
assert(!node.isConst);
final type = RecordType(node.recordType);
for (final expr in node.positional) {
_translateNode(expr);
}
for (final expr in node.named) {
_translateNode(expr.value);
}
assert(node.positional.length + node.named.length == type.numFields);
builder.addAllocateRecordLiteral(type);
}
}
/// Mapping between AST nodes and CFG IR [LocalVariable].
+5 -1
View File
@@ -30,6 +30,7 @@ class AstToIrTypes
.doubleType => DoubleType(node),
.boolType => BoolType(node),
.stringType => StringType(node),
.recordType => RecordType(node as ast.RecordType),
.objectType => ObjectType(node),
.nullType => const NullType(),
.neverType => const NeverType(),
@@ -107,7 +108,10 @@ class AstToIrTypes
node.extensionTypeErasure.accept(this);
@override
TypeKind visitRecordType(ast.RecordType node) => TypeKind.otherDartType;
TypeKind visitRecordType(ast.RecordType node) =>
node.nullability == .nonNullable
? TypeKind.recordType
: TypeKind.otherDartType;
@override
TypeKind visitFutureOrType(ast.FutureOrType node) {
+13
View File
@@ -118,3 +118,16 @@ class ClosureLayout {
(hasFunctionTypeArgs ? 1 : 0);
}
}
/// Field of the record object.
final class RecordField extends SyntheticField {
final RecordShape shape;
final int index;
RecordField(this.shape, this.index)
: super(
'#record-field[$index${index >= shape.positional ? ':${shape.named[index - shape.positional]}' : ''}]',
type: const ast.DynamicType(),
isFinal: true,
);
}
+16
View File
@@ -599,6 +599,22 @@ class FlowGraphBuilder {
return instr;
}
/// Append [AllocateRecordLiteral] to the graph.
/// Takes elements from the stack as inputs.
AllocateRecordLiteral addAllocateRecordLiteral(RecordType type) {
final inputCount = type.numFields;
final instr = AllocateRecordLiteral(
graph,
currentSourcePosition,
type,
inputCount: inputCount,
);
popInputs(instr, 0, inputCount);
push(instr);
appendInstruction(instr);
return instr;
}
/// Append [StringInterpolation] to the graph.
StringInterpolation addStringInterpolation(int inputCount) {
final instr = StringInterpolation(
+6
View File
@@ -412,6 +412,9 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
verifyTypeArgumentsInput(instr.typeArguments, instr);
}
@override
void visitAllocateRecordLiteral(AllocateRecordLiteral instr) {}
@override
void visitStringInterpolation(StringInterpolation instr) {}
@@ -487,6 +490,9 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor<void> {
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitAllocateRecord(AllocateRecord instr) {}
@override
void visitBoxInt(BoxInt instr) {
assert(instr.operand.type is IntType);
+31
View File
@@ -1392,6 +1392,24 @@ final class AllocateMapLiteral extends Definition with CanThrow, Pure {
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateMapLiteral(this);
}
/// Allocate a new Record literal with given elements.
final class AllocateRecordLiteral extends Definition with CanThrow, Pure {
@override
final RecordType type;
AllocateRecordLiteral(
super.graph,
super.sourcePosition,
this.type, {
required super.inputCount,
}) : assert(inputCount == type.numFields);
Definition elementAt(int index) => inputDefAt(index);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateRecordLiteral(this);
}
/// Interpolate given objects into a String.
final class StringInterpolation extends Definition
with CanThrow, HasSideEffects {
@@ -1755,6 +1773,19 @@ final class SetListElement extends Instruction
R accept<R>(InstructionVisitor<R> v) => v.visitSetListElement(this);
}
/// Allocate a Record instance of given type.
final class AllocateRecord extends Definition
with CanThrow, Pure, BackendInstruction {
@override
final RecordType type;
AllocateRecord(super.graph, super.sourcePosition, this.type)
: super(inputCount: 0);
@override
R accept<R>(InstructionVisitor<R> v) => v.visitAllocateRecord(this);
}
/// Base class for boxing instructions.
abstract base class Box extends Definition
with CanThrow, Pure, BackendInstruction {
+1
View File
@@ -189,6 +189,7 @@ final class IrToText extends VoidInstructionVisitor {
UnaryDoubleOp() => 'UnaryDoubleOp ${instr.op.token}',
UnaryBoolOp() => 'UnaryBoolOp ${instr.op.token}',
ParallelMove() => 'ParallelMove ${instr.stage.name}',
AllocateRecord() => 'AllocateRecord ${instr.type}',
_ => instr.runtimeType.toString(),
};
}
+55
View File
@@ -38,6 +38,7 @@ enum TypeKind {
doubleType,
boolType,
stringType,
recordType,
objectType,
nullType,
neverType,
@@ -193,6 +194,60 @@ final class StringType extends CType {
String toString() => 'String';
}
/// Shape of the Dart record.
/// Records with the same shape are compatible wrt field access.
final class RecordShape {
// Number of positional fields.
final int positional;
// Named fields (sorted lexicographically).
final List<String> named;
const RecordShape(this.positional, this.named);
@override
String toString() =>
'Record[$positional${named.isNotEmpty ? ', named: $named' : ''}]';
@override
bool operator ==(Object other) =>
other is RecordShape &&
this.positional == other.positional &&
listEquals(this.named, other.named);
@override
int get hashCode =>
finalizeHash(combineHash(positional.hashCode, listHashCode(named)));
}
/// Non-nullable Dart record type.
final class RecordType extends CType {
@override
TypeKind get kind => TypeKind.recordType;
@override
final ast.RecordType dartType;
late final shape = RecordShape(dartType.positional.length, [
for (final namedType in dartType.named) namedType.name,
]);
RecordType(this.dartType) : assert(dartType.nullability == .nonNullable);
int get numFields => dartType.positional.length + dartType.named.length;
@override
bool get isNullable => false;
@override
CType get toNonNullableType => this;
@override
bool get canBeFuture => false;
@override
String toString() => dartType.getDisplayString();
}
/// Dart `Object` type.
final class ObjectType extends CType {
final ast.DartType? _dartType;
+6
View File
@@ -43,6 +43,7 @@ abstract interface class InstructionVisitor<R> {
R visitAllocateContext(AllocateContext instr);
R visitAllocateListLiteral(AllocateListLiteral instr);
R visitAllocateMapLiteral(AllocateMapLiteral instr);
R visitAllocateRecordLiteral(AllocateRecordLiteral instr);
R visitStringInterpolation(StringInterpolation instr);
R visitEnterSuspendableFunction(EnterSuspendableFunction instr);
R visitSuspend(Suspend instr);
@@ -55,6 +56,7 @@ abstract interface class InstructionVisitor<R> {
R visitCompareAndBranch(CompareAndBranch instr);
R visitAllocateList(AllocateList instr);
R visitSetListElement(SetListElement instr);
R visitAllocateRecord(AllocateRecord instr);
R visitBoxInt(BoxInt instr);
R visitBoxDouble(BoxDouble instr);
R visitUnboxInt(UnboxInt instr);
@@ -117,6 +119,8 @@ abstract mixin class DefaultInstructionVisitor<R>
defaultInstruction(instr);
R visitAllocateMapLiteral(AllocateMapLiteral instr) =>
defaultInstruction(instr);
R visitAllocateRecordLiteral(AllocateRecordLiteral instr) =>
defaultInstruction(instr);
R visitStringInterpolation(StringInterpolation instr) =>
defaultInstruction(instr);
R visitEnterSuspendableFunction(EnterSuspendableFunction instr) =>
@@ -133,6 +137,8 @@ abstract mixin class DefaultInstructionVisitor<R>
R visitAllocateList(AllocateList instr) => defaultBackendInstruction(instr);
R visitSetListElement(SetListElement instr) =>
defaultBackendInstruction(instr);
R visitAllocateRecord(AllocateRecord instr) =>
defaultBackendInstruction(instr);
R visitBoxInt(BoxInt instr) => defaultBackendInstruction(instr);
R visitBoxDouble(BoxDouble instr) => defaultBackendInstruction(instr);
R visitUnboxInt(UnboxInt instr) => defaultBackendInstruction(instr);
@@ -428,6 +428,11 @@ final class ConstantPropagation extends Pass
_setNonConstant(instr);
}
@override
void visitAllocateRecordLiteral(AllocateRecordLiteral instr) {
_setNonConstant(instr);
}
@override
void visitStringInterpolation(StringInterpolation instr) {
for (int i = 0, n = instr.inputCount; i < n; ++i) {
@@ -574,6 +579,11 @@ final class ConstantPropagation extends Pass
@override
void visitSetListElement(SetListElement instr) {}
@override
void visitAllocateRecord(AllocateRecord instr) {
_setNonConstant(instr);
}
@override
void visitBoxInt(BoxInt instr) {
_setNonConstant(instr);
+6
View File
@@ -249,6 +249,9 @@ final class Simplification extends Pass
@override
Instruction visitAllocateMapLiteral(AllocateMapLiteral instr) => instr;
@override
Instruction visitAllocateRecordLiteral(AllocateRecordLiteral instr) => instr;
@override
Instruction visitStringInterpolation(StringInterpolation instr) {
final buf = _StringInterpolationBuffer(constantFolding);
@@ -292,6 +295,9 @@ final class Simplification extends Pass
@override
Instruction visitSetListElement(SetListElement instr) => instr;
@override
Instruction visitAllocateRecord(AllocateRecord instr) => instr;
@override
Instruction visitBoxInt(BoxInt instr) => instr;
+49
View File
@@ -155,6 +155,55 @@ void main() {
expect(StringType().isSubtypeOf(StaticType(comparableType)), isTrue);
});
test('record', () {
final dartType1 = ast.RecordType(
[coreTypes.intNonNullableRawType, coreTypes.stringNullableRawType],
[],
.nonNullable,
);
final recordType1 = RecordType(dartType1);
expect(recordType1.kind, equals(TypeKind.recordType));
expect(recordType1.dartType, equals(dartType1));
expect(recordType1.hashCode, equals(RecordType(dartType1).hashCode));
expect(recordType1.isNullable, isFalse);
expect(recordType1.toNonNullableType, equals(recordType1));
final dartType2 = ast.RecordType(
[coreTypes.intNonNullableRawType],
[ast.NamedType('foo', coreTypes.boolNonNullableRawType)],
.nonNullable,
);
final recordType2 = RecordType(dartType2);
expect(recordType2.kind, equals(TypeKind.recordType));
expect(recordType2.dartType, equals(dartType2));
expect(recordType2.hashCode, equals(RecordType(dartType2).hashCode));
expect(recordType2.isNullable, isFalse);
expect(recordType2.toNonNullableType, equals(recordType2));
expect(recordType1.isSubtypeOf(IntType()), isFalse);
expect(recordType1.isSubtypeOf(DoubleType()), isFalse);
expect(recordType1.isSubtypeOf(BoolType()), isFalse);
expect(recordType1.isSubtypeOf(StringType()), isFalse);
expect(recordType1.isSubtypeOf(ObjectType()), isTrue);
expect(recordType1.isSubtypeOf(TopType()), isTrue);
expect(recordType1.isSubtypeOf(NullType()), isFalse);
expect(recordType1.isSubtypeOf(NeverType()), isFalse);
expect(recordType1.isSubtypeOf(recordType2), isFalse);
final recordType3 = RecordType(
ast.RecordType(
[coreTypes.intNullableRawType, coreTypes.objectNullableRawType],
[],
.nonNullable,
),
);
expect(recordType1.isSubtypeOf(recordType3), isTrue);
expect(
recordType1.isSubtypeOf(StaticType(coreTypes.recordNonNullableRawType)),
isTrue,
);
});
test('object', () {
final objectDartType = coreTypes.objectNonNullableRawType;
expect(ObjectType(), equals(ObjectType(objectDartType)));
+12
View File
@@ -33,4 +33,16 @@ void logical(bool x, bool Function() y, bool z) {
print(!(x && (y() || z)));
}
void recordLiterals<T>(int a, String b, T c) {
print((a,));
print((a, bbb: b));
print((a, b, c));
}
void recordFields<T>((int, String) r1, (int, {T foo, T bar}) r2) {
print(r1.$2);
print(r2.$1);
print(r2.bar);
}
void main() {}
+31
View File
@@ -113,6 +113,37 @@ B40 = JoinBlock(B37, B45, B44) idom:B28
DirectCall print(v59)
Return(v55)
--- recordLiterals
B0 = EntryBlock()
v19 = Constant(null)
v1 = Parameter(#functionTypeParameters)
v2 = Parameter(a)
v3 = Parameter(b)
v4 = Parameter(c)
TypeParameters(v1)
v8 = AllocateRecordLiteral(v2)
DirectCall print(v8)
v12 = AllocateRecordLiteral(v2, v3)
DirectCall print(v12)
v17 = AllocateRecordLiteral(v2, v3, v4)
DirectCall print(v17)
Return(v19)
--- recordFields
B0 = EntryBlock()
v15 = Constant(null)
v1 = Parameter(#functionTypeParameters)
v2 = Parameter(r1)
v3 = Parameter(r2)
TypeParameters(v1)
v7 = LoadInstanceField(#record-field[1], v2)
DirectCall print(v7)
v10 = LoadInstanceField(#record-field[0], v3)
DirectCall print(v10)
v13 = LoadInstanceField(#record-field[1:bar], v3)
DirectCall print(v13)
Return(v15)
--- main
B0 = EntryBlock()
v1 = Constant(null)
@@ -1461,6 +1461,50 @@ final class Arm64CodeGenerator extends CodeGenerator {
}
}
@override
void visitAllocateRecord(AllocateRecord instr) {
final instanceSize = roundUp(
vmOffsets.Record_elementsStartOffset +
instr.type.numFields * objectLayout.compressedWordSize,
objectAlignment(wordSize),
);
final resultReg = AllocationStub.resultReg;
assert(outputReg(instr) == resultReg);
final done = Label();
Label slowPath = addSlowPath(() {
_asm.unimplemented(
'Unimplemented: code generation for AllocateRecord slow path',
);
_asm.b(done);
});
_asm.loadImmediate(
AllocationStub.tagsReg,
vmOffsets.computeNewObjectTags(
ClassId.RecordCid,
instanceSize,
log2wordSize,
),
);
_asm.inlineAllocation(
resultReg,
AllocationStub.tagsReg,
AllocationStub.scratch1Reg,
AllocationStub.scratch2Reg,
instanceSize,
slowPath,
initializeFields: true,
);
final fieldReg = AllocationStub.scratch1Reg;
_asm.loadFromPool(fieldReg, instr.type.shape);
_asm.str(
fieldReg,
_asm.fieldAddress(resultReg, vmOffsets.Record_shape_offset),
);
_asm.bind(done);
}
@override
void visitBoxInt(BoxInt instr) {
var operandReg = inputReg(instr, 0);
@@ -360,6 +360,14 @@ final class Arm64Constraints extends Constraints {
const [anyCpuRegister, anyCpuRegister],
);
@override
InstructionConstraints? visitAllocateRecord(AllocateRecord instr) =>
const InstructionConstraints(AllocationStub.resultReg, [], [
AllocationStub.tagsReg,
AllocationStub.scratch1Reg,
AllocationStub.scratch2Reg,
]);
@override
InstructionConstraints? visitBoxInt(BoxInt instr) =>
const InstructionConstraints(
@@ -297,6 +297,10 @@ abstract base class CodeGenerator extends Pass
void visitAllocateMapLiteral(AllocateMapLiteral instr) =>
throw 'Unexpected AllocateMapLiteral (should be lowered)';
@override
void visitAllocateRecordLiteral(AllocateRecordLiteral instr) =>
throw 'Unexpected AllocateRecordLiteral (should be lowered)';
@override
void visitStringInterpolation(StringInterpolation instr) =>
throw 'Unexpected StringInterpolation (should be lowered)';
@@ -152,6 +152,11 @@ abstract base class Constraints
InstructionConstraints? visitAllocateMapLiteral(AllocateMapLiteral instr) =>
throw 'Unexpected AllocateMapLiteral (should be lowered)';
@override
InstructionConstraints? visitAllocateRecordLiteral(
AllocateRecordLiteral instr,
) => throw 'Unexpected AllocateRecordLiteral (should be lowered)';
@override
InstructionConstraints? visitStringInterpolation(StringInterpolation instr) =>
throw 'Unexpected StringInterpolation (should be lowered)';
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/field.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/instructions.dart';
@@ -246,6 +247,26 @@ final class Lowering extends Pass with DefaultInstructionVisitor<void> {
instr.removeFromGraph();
}
@override
void visitAllocateRecordLiteral(AllocateRecordLiteral instr) {
final obj = AllocateRecord(graph, instr.sourcePosition, instr.type);
obj.insertBefore(instr);
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final element = instr.elementAt(i);
// TODO: canonicalize record fields
final setElem = StoreInstanceField(
graph,
instr.sourcePosition,
CField(RecordField(instr.type.shape, i)),
obj,
element,
);
setElem.insertBefore(instr);
}
instr.replaceUsesWith(obj);
instr.removeFromGraph();
}
@override
void visitStringInterpolation(StringInterpolation instr) {
assert(instr.inputCount > 0);
@@ -53,6 +53,7 @@ class ObjectLayout {
return switch (field.asSynthetic) {
ContextField(:var index) => vmOffsets.Context_elementOffset(index),
ClosureField(:var index) => vmOffsets.Closure_elementOffset(index),
RecordField(:var index) => vmOffsets.Record_elementOffset(index),
};
}
_ensureComputed(field.enclosingClass);
+131 -2
View File
@@ -9,6 +9,7 @@ import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/field.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/utils/misc.dart';
import 'package:kernel/ast.dart' as ast;
import 'package:kernel/src/printer.dart' as ast_printer show AstPrinter;
@@ -63,6 +64,7 @@ enum PredefinedClusters {
closureFunctionRefs,
closureRefs,
argumentsDescriptorRefs,
recordShapeRefs,
ints,
doubles,
lists,
@@ -320,6 +322,7 @@ class SnapshotSerializer {
ArgumentsShape() => getPredefinedCluster(
PredefinedClusters.argumentsDescriptorRefs,
),
RecordShape() => getPredefinedCluster(PredefinedClusters.recordShapeRefs),
// Constants.
String() => getPredefinedCluster(
OneByteStringSerializationCluster.isOneByteString(obj)
@@ -385,6 +388,7 @@ class SnapshotSerializer {
.closureFunctionRefs => ClosureFunctionRefSerializationCluster(),
.closureRefs => ClosureRefSerializationCluster(),
.argumentsDescriptorRefs => ArgumentsDescriptorRefSerializationCluster(),
.recordShapeRefs => RecordShapeRefSerializationCluster(),
.oneByteStrings => OneByteStringSerializationCluster(),
.twoByteStrings => TwoByteStringSerializationCluster(),
.privateNames => PrivateNameSerializationCluster(),
@@ -393,14 +397,14 @@ class SnapshotSerializer {
.lists => ListSerializationCluster(),
.maps => MapSerializationCluster(),
.sets => SetSerializationCluster(),
.records => throw 'Unimplemented cluster $clusterId',
.records => RecordSerializationCluster(),
.instantiatedClosures => throw 'Unimplemented cluster $clusterId',
.typeParameters =>
throw 'Unimplemented cluster $clusterId', // TypeParametersSerializationCluster(),
.typeArguments => TypeArgumentsSerializationCluster(),
.interfaceTypes => InterfaceTypeSerializationCluster(),
.functionTypes => FunctionTypeSerializationCluster(),
.recordTypes => throw 'Unimplemented cluster $clusterId',
.recordTypes => RecordTypeSerializationCluster(),
.typeParameterTypes => TypeParameterTypeSerializationCluster(),
.codes => CodeSerializationCluster(),
.icDatas => ICDataSerializationCluster(),
@@ -686,6 +690,33 @@ final class ArgumentsDescriptorRefSerializationCluster
}
}
final class RecordShapeRefSerializationCluster extends SerializationCluster {
final List<RecordShape> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final shape = object as RecordShape;
_objects.add(shape);
for (final name in shape.named) {
serializer.push(name);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.recordShapeRefs.index);
serializer.writeUint(_objects.length);
for (final shape in _objects) {
serializer.assignRef(shape);
serializer.writeUint(shape.positional);
serializer.writeUint(shape.named.length);
for (final name in shape.named) {
serializer.writeRefId(name);
}
}
}
}
final class OneByteStringSerializationCluster extends SerializationCluster {
final List<String> _objects = [];
@@ -971,6 +1002,56 @@ final class SetSerializationCluster extends SerializationCluster {
}
}
/// Serialization cluster for constant records.
final class RecordSerializationCluster extends SerializationCluster {
final List<ast.RecordConstant> _objects = [];
final List<RecordShape> _shapes = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final record = object as ast.RecordConstant;
final shape = RecordType(record.recordType).shape;
_objects.add(record);
_shapes.add(shape);
serializer.push(shape);
for (final e in record.positional) {
serializer.push(e);
}
for (final e in record.named.values) {
serializer.push(e);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.records.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final record in _objects) {
serializer.assignRef(record);
serializer.writeUint(record.positional.length + record.named.length);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0, n = _objects.length; i < n; ++i) {
final record = _objects[i];
final shape = _shapes[i];
serializer.writeRefId(shape);
for (final e in record.positional) {
serializer.writeRefId(e);
}
for (final name in shape.named) {
serializer.writeRefId(record.named[name]!);
}
}
}
}
final class InstanceSerializationCluster extends SerializationCluster {
final ast.Class _cls;
final List<ast.InstanceConstant> _objects = [];
@@ -1221,6 +1302,54 @@ final class FunctionTypeSerializationCluster extends SerializationCluster {
}
}
final class RecordTypeSerializationCluster extends SerializationCluster {
final List<ast.RecordType> _objects = [];
final List<RecordShape> _shapes = [];
final List<ast.ListConstant> _fieldTypes = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final type = object as ast.RecordType;
final shape = RecordType(type.withDeclaredNullability(.nonNullable)).shape;
final fieldTypes = getListConstant([
...type.positional,
for (final nt in type.named) nt.type,
]);
_objects.add(type);
_shapes.add(shape);
_fieldTypes.add(fieldTypes);
serializer.push(shape);
serializer.push(fieldTypes);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.recordTypes.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final type in _objects) {
serializer.assignRef(type);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0, n = _objects.length; i < n; ++i) {
final type = _objects[i];
final shape = _shapes[i];
final fieldTypes = _fieldTypes[i];
serializer.writeUint(
type.declaredNullability == ast.Nullability.nullable ? 1 : 0,
);
serializer.writeRefId(shape);
serializer.writeRefId(fieldTypes);
}
}
}
final class TypeParameterTypeSerializationCluster extends SerializationCluster {
final List<ast.DartType> _objects = [];
@@ -65,6 +65,13 @@ void mapLiterals<S, T>(S key, T Function() value, S key2, T value2) {
print({key: value(), key2: value2});
}
void recordLiterals<T>(int a, String b, T c) {
print((a,));
print((a, b, c));
print((foo: b, bar: c));
print((a, foo: b, bar: c));
}
void stringInterpolation(int x, String s, Object o) {
print('$o');
print('Hey, x=$x, s=$s, o=$o');
@@ -325,6 +325,43 @@ B0 = EntryBlock()
ParallelMove input(NullConstant(null) -> R0)
Return(v11) # RA: (R0)
--- recordLiterals
B0 = EntryBlock()
v30 = Constant(null)
Parameter(#functionTypeParameters) # RA: param[0] <- ()
v2 = Parameter(a) # RA: param[1] <- ()
v3 = Parameter(b) # RA: param[2] <- ()
v4 = Parameter(c) # RA: param[3] <- ()
ParallelMove output(param[3] -> vloc:R6, param[2] -> vloc:R5, param[1] -> vloc:R1)
v32 = AllocateRecord (int)() # RA: R0 <- () temps: [R2, R3, R4]
ParallelMove output(R0 -> vloc:R0)
StoreInstanceField(#record-field[0], v32, v2) # RA: (R0, R1) temps: [R2, R3]
DirectCall print(v32) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
v34 = AllocateRecord (int, String, recordLiterals.T%)() # RA: R0 <- () temps: [R2, R3, R4]
ParallelMove output(R0 -> vloc:R0, param[1] -> R3)
StoreInstanceField(#record-field[0], v34, v2) # RA: (R0, R3) temps: [R1, R2]
ParallelMove output(param[2] -> R4)
StoreInstanceField(#record-field[1], v34, v3) # RA: (R0, R4) temps: [R1, R2]
ParallelMove output(param[3] -> R5)
StoreInstanceField(#record-field[2], v34, v4) # RA: (R0, R5) temps: [R1, R2]
DirectCall print(v34) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
v38 = AllocateRecord ({recordLiterals.T% bar, String foo})() # RA: R0 <- () temps: [R2, R3, R4]
ParallelMove output(R0 -> vloc:R0, param[3] -> R3)
StoreInstanceField(#record-field[0:bar], v38, v4) # RA: (R0, R3) temps: [R1, R2]
ParallelMove output(param[2] -> R4)
StoreInstanceField(#record-field[1:foo], v38, v3) # RA: (R0, R4) temps: [R1, R2]
DirectCall print(v38) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
v41 = AllocateRecord (int, {recordLiterals.T% bar, String foo})() # RA: R0 <- () temps: [R2, R3, R4]
ParallelMove output(R0 -> vloc:R0, param[1] -> R3)
StoreInstanceField(#record-field[0], v41, v2) # RA: (R0, R3) temps: [R1, R2]
ParallelMove output(param[3] -> R3)
StoreInstanceField(#record-field[1:bar], v41, v4) # RA: (R0, R3) temps: [R1, R2]
ParallelMove output(param[2] -> R3)
StoreInstanceField(#record-field[2:foo], v41, v3) # RA: (R0, R3) temps: [R1, R2]
DirectCall print(v41) # RA: R0 <- (R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30]
ParallelMove input(NullConstant(null) -> R0)
Return(v30) # RA: (R0)
--- stringInterpolation
B0 = EntryBlock()
v7 = Constant("Hey, x=")
+124 -7
View File
@@ -58,6 +58,7 @@ class ModuleSnapshot : public AllStatic {
kClosureFunctionRefs,
kClosureRefs,
kArgumentsDescriptorRefs,
kRecordShapeRefs,
kInts,
kDoubles,
kLists,
@@ -623,6 +624,42 @@ class ArgumentsDescriptorRefDeserializationCluster
Array& args_descriptor_;
};
class RecordShapeRefDeserializationCluster : public DeserializationCluster {
public:
explicit RecordShapeRefDeserializationCluster(Zone* zone)
: DeserializationCluster("RecordShapeRef"),
name_(String::Handle(zone)),
named_(Array::Handle(zone)),
shape_(Smi::Handle(zone)) {}
~RecordShapeRefDeserializationCluster() {}
void PreLoad(Deserializer* d) override {
const intptr_t count = d->ReadUnsigned();
for (intptr_t i = 0; i < count; i++) {
const intptr_t num_positional = d->ReadUnsigned();
const intptr_t num_named = d->ReadUnsigned();
const intptr_t num_fields = num_positional + num_named;
const Array* field_names = &Array::empty_array();
if (num_named > 0) {
named_ = Array::New(num_named, Heap::kOld);
for (intptr_t i = 0; i < num_named; ++i) {
name_ ^= d->ReadRef();
named_.SetAt(i, name_);
}
named_.MakeImmutable();
field_names = &named_;
}
shape_ =
RecordShape::Register(d->thread(), num_fields, *field_names).AsSmi();
d->AssignRefPreLoad(shape_);
}
}
private:
String& name_;
Array& named_;
Smi& shape_;
};
class IntDeserializationCluster : public DeserializationCluster {
public:
IntDeserializationCluster()
@@ -796,6 +833,42 @@ class SetDeserializationCluster : public DeserializationCluster {
}
};
class RecordDeserializationCluster : public DeserializationCluster {
public:
RecordDeserializationCluster()
: DeserializationCluster(
"Record",
Object::ShouldHaveDeeplyImmutabilityBitSet(kRecordCid)) {}
~RecordDeserializationCluster() {}
void ReadAlloc(Deserializer* d) override {
start_index_ = d->next_index();
const intptr_t count = d->ReadUnsigned();
for (intptr_t i = 0; i < count; i++) {
const intptr_t length = d->ReadUnsigned();
d->AssignRef(d->Allocate(Record::InstanceSize(length)));
}
stop_index_ = d->next_index();
}
void ReadFill(Deserializer* d_) override {
Deserializer::Local d(d_);
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
RecordPtr record = static_cast<RecordPtr>(d.Ref(id));
SmiPtr shape = static_cast<SmiPtr>(d.ReadRef());
const intptr_t num_fields = RecordShape(shape).num_fields();
Deserializer::InitializeHeader(record, kRecordCid,
Record::InstanceSize(num_fields),
is_deeply_immutable());
record->untag()->shape_ = shape;
for (intptr_t j = 0; j < num_fields; j++) {
record->untag()->data()[j] = d.ReadRef();
}
}
}
};
class InstanceDeserializationCluster : public DeserializationCluster {
public:
explicit InstanceDeserializationCluster(const Class& cls)
@@ -962,6 +1035,52 @@ class FunctionTypeDeserializationCluster : public DeserializationCluster {
}
};
class RecordTypeDeserializationCluster : public DeserializationCluster {
public:
RecordTypeDeserializationCluster()
: DeserializationCluster(
"RecordType",
Object::ShouldHaveDeeplyImmutabilityBitSet(kRecordTypeCid)) {}
~RecordTypeDeserializationCluster() {}
void ReadAlloc(Deserializer* d) override {
ReadAllocFixedSize(d, RecordType::InstanceSize());
}
void ReadFill(Deserializer* d_) override {
Deserializer::Local d(d_);
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
RecordTypePtr type = static_cast<RecordTypePtr>(d.Ref(id));
Deserializer::InitializeHeader(type, kRecordTypeCid,
RecordType::InstanceSize(),
is_deeply_immutable());
type->untag()->type_test_stub_entry_point_.store(
0, std::memory_order_relaxed);
const intptr_t is_nullable = d.ReadUnsigned();
const intptr_t flags = UntaggedAbstractType::NullabilityBit::update(
is_nullable, UntaggedAbstractType::TypeStateBits::encode(
UntaggedAbstractType::kAllocated));
type->untag()->set_flags(flags);
type->untag()->type_test_stub_ = static_cast<CodePtr>(d.null());
type->untag()->hash_ = Smi::New(0);
type->untag()->shape_ = static_cast<SmiPtr>(d.ReadRef());
type->untag()->field_types_ = static_cast<ArrayPtr>(d.ReadRef());
}
}
void PostLoad(Deserializer* d, const Array& refs) override {
RecordType& type = RecordType::Handle(d->zone());
Code& stub = Code::Handle(d->zone());
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
type ^= refs.At(id);
stub = TypeTestingStubGenerator::DefaultCodeForType(type);
type.InitializeTypeTestingStubNonAtomic(stub);
type.SetIsFinalized();
}
}
};
class InterfaceTypeDeserializationCluster : public DeserializationCluster {
public:
InterfaceTypeDeserializationCluster()
@@ -1311,7 +1430,7 @@ class ObjectPoolDeserializationCluster : public DeserializationCluster {
continue;
}
obj = pool.ObjectAt(i);
if (obj.IsInstance() && !obj.InVMIsolateHeap()) {
if (obj.IsInstance() && !obj.IsSmi() && !obj.InVMIsolateHeap()) {
obj = Instance::Cast(obj).Canonicalize(d->thread());
pool.SetObjectAt(i, obj);
}
@@ -1392,6 +1511,8 @@ DeserializationCluster* Deserializer::ReadCluster() {
return new (Z) ClosureRefDeserializationCluster(Z);
case ModuleSnapshot::kArgumentsDescriptorRefs:
return new (Z) ArgumentsDescriptorRefDeserializationCluster(Z);
case ModuleSnapshot::kRecordShapeRefs:
return new (Z) RecordShapeRefDeserializationCluster(Z);
case ModuleSnapshot::kInts:
return new (Z) IntDeserializationCluster();
case ModuleSnapshot::kDoubles:
@@ -1403,9 +1524,7 @@ DeserializationCluster* Deserializer::ReadCluster() {
case ModuleSnapshot::kSets:
return new (Z) SetDeserializationCluster();
case ModuleSnapshot::kRecords:
// return new (Z) RecordDeserializationCluster();
UNIMPLEMENTED();
return nullptr;
return new (Z) RecordDeserializationCluster();
case ModuleSnapshot::kInstantiatedClosures:
// return new (Z) InstantiatedClosureDeserializationCluster();
UNIMPLEMENTED();
@@ -1419,9 +1538,7 @@ DeserializationCluster* Deserializer::ReadCluster() {
case ModuleSnapshot::kFunctionTypes:
return new (Z) FunctionTypeDeserializationCluster();
case ModuleSnapshot::kRecordTypes:
// return new (Z) RecordTypeDeserializationCluster();
UNIMPLEMENTED();
return nullptr;
return new (Z) RecordTypeDeserializationCluster();
case ModuleSnapshot::kTypeParameterTypes:
return new (Z) TypeParameterTypeDeserializationCluster();
case ModuleSnapshot::kTypeArguments:
+5
View File
@@ -57,6 +57,8 @@ class InterfaceTypeDeserializationCluster;
class ListDeserializationCluster;
class MapDeserializationCluster;
class ObjectPoolDeserializationCluster;
class RecordDeserializationCluster;
class RecordTypeDeserializationCluster;
class SetDeserializationCluster;
class SubtypeTestCacheDeserializationCluster;
class TypeArgumentsDeserializationCluster;
@@ -3088,6 +3090,8 @@ class UntaggedRecordType : public UntaggedAbstractType {
VISIT_TO(field_types)
CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) { return to(); }
friend class module_snapshot::RecordTypeDeserializationCluster;
};
class UntaggedTypeParameter : public UntaggedAbstractType {
@@ -3644,6 +3648,7 @@ class UntaggedRecord : public UntaggedInstance {
// Variable length data follows here.
COMPRESSED_VARIABLE_POINTER_FIELDS(ObjectPtr, field, data, shape)
friend class module_snapshot::RecordDeserializationCluster;
friend void UpdateLengthField(intptr_t, ObjectPtr,
ObjectPtr); // shape_
};