From ccbcd76265814e64e796df27a5dc41b0ca2bbf73 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Mon, 18 May 2026 08:03:32 -0700 Subject: [PATCH] [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 Reviewed-by: Slava Egorov --- pkg/cfg/lib/front_end/ast_to_ir.dart | 34 +++++ pkg/cfg/lib/front_end/ast_to_ir_types.dart | 6 +- pkg/cfg/lib/ir/field.dart | 13 ++ pkg/cfg/lib/ir/flow_graph_builder.dart | 16 +++ pkg/cfg/lib/ir/flow_graph_checker.dart | 6 + pkg/cfg/lib/ir/instructions.dart | 31 ++++ pkg/cfg/lib/ir/ir_to_text.dart | 1 + pkg/cfg/lib/ir/types.dart | 55 ++++++++ pkg/cfg/lib/ir/visitor.dart | 6 + pkg/cfg/lib/passes/constant_propagation.dart | 10 ++ pkg/cfg/lib/passes/simplification.dart | 6 + pkg/cfg/test/ir/types_test.dart | 49 +++++++ pkg/cfg/testcases/expressions.dart | 12 ++ pkg/cfg/testcases/expressions.dart.expect | 31 ++++ .../lib/back_end/arm64/code_generator.dart | 44 ++++++ .../lib/back_end/arm64/constraints.dart | 8 ++ .../lib/back_end/code_generator.dart | 4 + .../lib/back_end/constraints.dart | 5 + pkg/native_compiler/lib/passes/lowering.dart | 21 +++ .../lib/runtime/object_layout.dart | 1 + .../lib/snapshot/snapshot.dart | 133 +++++++++++++++++- .../testcases/lowering_test.dart | 7 + .../testcases/lowering_test.dart.expect | 37 +++++ runtime/vm/module_snapshot.cc | 131 ++++++++++++++++- runtime/vm/raw_object.h | 5 + 25 files changed, 662 insertions(+), 10 deletions(-) diff --git a/pkg/cfg/lib/front_end/ast_to_ir.dart b/pkg/cfg/lib/front_end/ast_to_ir.dart index 07457ffc45e..644ba076d0c 100644 --- a/pkg/cfg/lib/front_end/ast_to_ir.dart +++ b/pkg/cfg/lib/front_end/ast_to_ir.dart @@ -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]. diff --git a/pkg/cfg/lib/front_end/ast_to_ir_types.dart b/pkg/cfg/lib/front_end/ast_to_ir_types.dart index 676ae0746df..95c3954742a 100644 --- a/pkg/cfg/lib/front_end/ast_to_ir_types.dart +++ b/pkg/cfg/lib/front_end/ast_to_ir_types.dart @@ -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) { diff --git a/pkg/cfg/lib/ir/field.dart b/pkg/cfg/lib/ir/field.dart index 3e30f025bcc..7b348ea506f 100644 --- a/pkg/cfg/lib/ir/field.dart +++ b/pkg/cfg/lib/ir/field.dart @@ -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, + ); +} diff --git a/pkg/cfg/lib/ir/flow_graph_builder.dart b/pkg/cfg/lib/ir/flow_graph_builder.dart index c9b1d03dbd5..7801127da03 100644 --- a/pkg/cfg/lib/ir/flow_graph_builder.dart +++ b/pkg/cfg/lib/ir/flow_graph_builder.dart @@ -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( diff --git a/pkg/cfg/lib/ir/flow_graph_checker.dart b/pkg/cfg/lib/ir/flow_graph_checker.dart index e549b97f736..994bb07a14f 100644 --- a/pkg/cfg/lib/ir/flow_graph_checker.dart +++ b/pkg/cfg/lib/ir/flow_graph_checker.dart @@ -412,6 +412,9 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor { 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 { @override void visitSetListElement(SetListElement instr) {} + @override + void visitAllocateRecord(AllocateRecord instr) {} + @override void visitBoxInt(BoxInt instr) { assert(instr.operand.type is IntType); diff --git a/pkg/cfg/lib/ir/instructions.dart b/pkg/cfg/lib/ir/instructions.dart index 189043a4594..1ba54aed865 100644 --- a/pkg/cfg/lib/ir/instructions.dart +++ b/pkg/cfg/lib/ir/instructions.dart @@ -1392,6 +1392,24 @@ final class AllocateMapLiteral extends Definition with CanThrow, Pure { R accept(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor 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(InstructionVisitor v) => v.visitAllocateRecord(this); +} + /// Base class for boxing instructions. abstract base class Box extends Definition with CanThrow, Pure, BackendInstruction { diff --git a/pkg/cfg/lib/ir/ir_to_text.dart b/pkg/cfg/lib/ir/ir_to_text.dart index 79d7c5aa458..4c3df83fa67 100644 --- a/pkg/cfg/lib/ir/ir_to_text.dart +++ b/pkg/cfg/lib/ir/ir_to_text.dart @@ -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(), }; } diff --git a/pkg/cfg/lib/ir/types.dart b/pkg/cfg/lib/ir/types.dart index e7309d53653..65be91cc430 100644 --- a/pkg/cfg/lib/ir/types.dart +++ b/pkg/cfg/lib/ir/types.dart @@ -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 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; diff --git a/pkg/cfg/lib/ir/visitor.dart b/pkg/cfg/lib/ir/visitor.dart index 1794f3a8af4..4085552a4a0 100644 --- a/pkg/cfg/lib/ir/visitor.dart +++ b/pkg/cfg/lib/ir/visitor.dart @@ -43,6 +43,7 @@ abstract interface class InstructionVisitor { 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 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 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 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); diff --git a/pkg/cfg/lib/passes/constant_propagation.dart b/pkg/cfg/lib/passes/constant_propagation.dart index 5a263fb1018..56aeea7528b 100644 --- a/pkg/cfg/lib/passes/constant_propagation.dart +++ b/pkg/cfg/lib/passes/constant_propagation.dart @@ -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); diff --git a/pkg/cfg/lib/passes/simplification.dart b/pkg/cfg/lib/passes/simplification.dart index 26af951815d..ac4b4e891f5 100644 --- a/pkg/cfg/lib/passes/simplification.dart +++ b/pkg/cfg/lib/passes/simplification.dart @@ -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; diff --git a/pkg/cfg/test/ir/types_test.dart b/pkg/cfg/test/ir/types_test.dart index 6201da2d3dc..c1c14fbfd17 100644 --- a/pkg/cfg/test/ir/types_test.dart +++ b/pkg/cfg/test/ir/types_test.dart @@ -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))); diff --git a/pkg/cfg/testcases/expressions.dart b/pkg/cfg/testcases/expressions.dart index 9b5ca7deb83..40fb539d516 100644 --- a/pkg/cfg/testcases/expressions.dart +++ b/pkg/cfg/testcases/expressions.dart @@ -33,4 +33,16 @@ void logical(bool x, bool Function() y, bool z) { print(!(x && (y() || z))); } +void recordLiterals(int a, String b, T c) { + print((a,)); + print((a, bbb: b)); + print((a, b, c)); +} + +void recordFields((int, String) r1, (int, {T foo, T bar}) r2) { + print(r1.$2); + print(r2.$1); + print(r2.bar); +} + void main() {} diff --git a/pkg/cfg/testcases/expressions.dart.expect b/pkg/cfg/testcases/expressions.dart.expect index 9652b83a487..739ec174264 100644 --- a/pkg/cfg/testcases/expressions.dart.expect +++ b/pkg/cfg/testcases/expressions.dart.expect @@ -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) diff --git a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart index 44907d3b5b7..9f6be37fe91 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -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); diff --git a/pkg/native_compiler/lib/back_end/arm64/constraints.dart b/pkg/native_compiler/lib/back_end/arm64/constraints.dart index f7aa4319c06..c924dcc0922 100644 --- a/pkg/native_compiler/lib/back_end/arm64/constraints.dart +++ b/pkg/native_compiler/lib/back_end/arm64/constraints.dart @@ -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( diff --git a/pkg/native_compiler/lib/back_end/code_generator.dart b/pkg/native_compiler/lib/back_end/code_generator.dart index b176d69f4f9..4b31bd17a07 100644 --- a/pkg/native_compiler/lib/back_end/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/code_generator.dart @@ -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)'; diff --git a/pkg/native_compiler/lib/back_end/constraints.dart b/pkg/native_compiler/lib/back_end/constraints.dart index 797d2b180df..b3e06f6f0b0 100644 --- a/pkg/native_compiler/lib/back_end/constraints.dart +++ b/pkg/native_compiler/lib/back_end/constraints.dart @@ -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)'; diff --git a/pkg/native_compiler/lib/passes/lowering.dart b/pkg/native_compiler/lib/passes/lowering.dart index f9add310929..801986834b6 100644 --- a/pkg/native_compiler/lib/passes/lowering.dart +++ b/pkg/native_compiler/lib/passes/lowering.dart @@ -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 { 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); diff --git a/pkg/native_compiler/lib/runtime/object_layout.dart b/pkg/native_compiler/lib/runtime/object_layout.dart index 6a1cafc3802..a7f7d99dd37 100644 --- a/pkg/native_compiler/lib/runtime/object_layout.dart +++ b/pkg/native_compiler/lib/runtime/object_layout.dart @@ -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); diff --git a/pkg/native_compiler/lib/snapshot/snapshot.dart b/pkg/native_compiler/lib/snapshot/snapshot.dart index bd285468cf0..e5dc4c24f58 100644 --- a/pkg/native_compiler/lib/snapshot/snapshot.dart +++ b/pkg/native_compiler/lib/snapshot/snapshot.dart @@ -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 _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 _objects = []; @@ -971,6 +1002,56 @@ final class SetSerializationCluster extends SerializationCluster { } } +/// Serialization cluster for constant records. +final class RecordSerializationCluster extends SerializationCluster { + final List _objects = []; + final List _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 _objects = []; @@ -1221,6 +1302,54 @@ final class FunctionTypeSerializationCluster extends SerializationCluster { } } +final class RecordTypeSerializationCluster extends SerializationCluster { + final List _objects = []; + final List _shapes = []; + final List _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 _objects = []; diff --git a/pkg/native_compiler/testcases/lowering_test.dart b/pkg/native_compiler/testcases/lowering_test.dart index f7e7e2fed39..bf0d7f27c29 100644 --- a/pkg/native_compiler/testcases/lowering_test.dart +++ b/pkg/native_compiler/testcases/lowering_test.dart @@ -65,6 +65,13 @@ void mapLiterals(S key, T Function() value, S key2, T value2) { print({key: value(), key2: value2}); } +void recordLiterals(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'); diff --git a/pkg/native_compiler/testcases/lowering_test.dart.expect b/pkg/native_compiler/testcases/lowering_test.dart.expect index 8a945591e7c..34dcbfea31e 100644 --- a/pkg/native_compiler/testcases/lowering_test.dart.expect +++ b/pkg/native_compiler/testcases/lowering_test.dart.expect @@ -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=") diff --git a/runtime/vm/module_snapshot.cc b/runtime/vm/module_snapshot.cc index 1c74bf194b7..d28b8e40946 100644 --- a/runtime/vm/module_snapshot.cc +++ b/runtime/vm/module_snapshot.cc @@ -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(d.Ref(id)); + SmiPtr shape = static_cast(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(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(d.null()); + type->untag()->hash_ = Smi::New(0); + type->untag()->shape_ = static_cast(d.ReadRef()); + type->untag()->field_types_ = static_cast(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: diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 6c2f226dca0..edaa2a68276 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -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_ };