From 0ec57dd5619c18958246fec8fc3a65160177fbb9 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Tue, 24 Feb 2026 08:54:39 -0800 Subject: [PATCH] [vm,modular_aot] Initial code generation for static field loads and stores TEST=ci Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: I07aa7cabf8fe78c0f98c17b98ae85bdc903b99c3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482920 Reviewed-by: Slava Egorov Commit-Queue: Alexander Markov --- pkg/cfg/lib/ir/constant_value.dart | 25 +++++ pkg/cfg/lib/ir/field.dart | 1 + .../lib/back_end/arm64/code_generator.dart | 102 ++++++++++++++++-- .../lib/back_end/arm64/constraints.dart | 27 +++-- .../lib/back_end/object_pool.dart | 15 +++ pkg/native_compiler/lib/configuration.dart | 12 ++- .../lib/snapshot/snapshot.dart | 14 ++- runtime/vm/module_snapshot.cc | 10 ++ 8 files changed, 183 insertions(+), 23 deletions(-) diff --git a/pkg/cfg/lib/ir/constant_value.dart b/pkg/cfg/lib/ir/constant_value.dart index d09ff888acd..f6a8e5d4ddb 100644 --- a/pkg/cfg/lib/ir/constant_value.dart +++ b/pkg/cfg/lib/ir/constant_value.dart @@ -285,3 +285,28 @@ class TypeArgumentsConstant extends ast.AuxiliaryConstant { @override ast.DartType getType(StaticTypeContext context) => const ast.DynamicType(); } + +/// Synthetic sentinel value which can be used by certain back-ends to +/// represent the uninitialized value of a late or static field, late variable or +/// value of an optional parameter which was not passed. +class SentinelConstant extends ast.AuxiliaryConstant { + SentinelConstant(); + + @override + void visitChildren(ast.Visitor v) {} + + @override + void toTextInternal(ast_printer.AstPrinter printer) => '#sentinel'; + + @override + String toString() => toStringInternal(); + + @override + int get hashCode => 2031; + + @override + bool operator ==(Object other) => other is SentinelConstant; + + @override + ast.DartType getType(StaticTypeContext context) => const ast.DynamicType(); +} diff --git a/pkg/cfg/lib/ir/field.dart b/pkg/cfg/lib/ir/field.dart index f3aec561e83..a1389d9da82 100644 --- a/pkg/cfg/lib/ir/field.dart +++ b/pkg/cfg/lib/ir/field.dart @@ -13,4 +13,5 @@ extension type CField(ast.Field _raw) { bool get hasInitializer => _raw.initializer != null; CType get type => CType.fromStaticType(_raw.type); ast.Class get enclosingClass => _raw.enclosingClass!; + ast.Field get astField => _raw; } 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 034e6c62a1e..ccd3cc35059 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -3,6 +3,8 @@ // 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/instructions.dart'; import 'package:cfg/ir/types.dart'; import 'package:cfg/utils/misc.dart'; @@ -17,9 +19,10 @@ import 'package:native_compiler/runtime/type_utils.dart'; import 'package:native_compiler/runtime/vm_defs.dart'; final class Arm64CodeGenerator extends CodeGenerator { + final FunctionRegistry functionRegistry; late final Arm64Assembler _asm; - Arm64CodeGenerator(super.backEndState); + Arm64CodeGenerator(super.backEndState, this.functionRegistry); @override Assembler createAssembler() => _asm = Arm64Assembler(backEndState.vmOffsets); @@ -240,12 +243,9 @@ final class Arm64CodeGenerator extends CodeGenerator { assert(offset <= stackFrame.maxArgumentsStackSlots * wordSize); } - @override - void visitDirectCall(DirectCall instr) { - _passArguments(instr); - _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); - _asm.loadFromPool(functionReg, instr.target); + void _callFunction(CFunction function) { // TODO: call directly through Code. + _asm.loadFromPool(functionReg, function); _asm.ldr( codeReg, _asm.fieldAddress(functionReg, vmOffsets.Function_code_offset), @@ -260,6 +260,13 @@ final class Arm64CodeGenerator extends CodeGenerator { _asm.blr(tempReg); } + @override + void visitDirectCall(DirectCall instr) { + _passArguments(instr); + _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); + _callFunction(instr.target); + } + @override void visitInterfaceCall(InterfaceCall instr) { _passArguments(instr); @@ -423,14 +430,93 @@ final class Arm64CodeGenerator extends CodeGenerator { } } + void _loadStaticFieldAddress(Register dst, CField field, Register scratch) { + _asm.ldr( + scratch, + _asm.address(threadReg, vmOffsets.Thread_field_table_values_offset), + ); + _asm.loadFromPool(dst, StaticFieldOffset(field)); + _asm.add(dst, dst, scratch); + } + @override void visitLoadStaticField(LoadStaticField instr) { - _asm.unimplemented('Unimplemented: code generation for LoadStaticField'); + final field = instr.field; + final valueReg = outputReg(instr); + final scratch1Reg = temporaryReg(instr, 0); + final scratch2Reg = temporaryReg(instr, 1); + + // TODO: shared static fields + _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); + _asm.ldr(valueReg, RegOffsetAddress(scratch1Reg, 0)); + + if (instr.checkInitialized) { + _asm.loadFromPool(scratch2Reg, SentinelConstant()); + _asm.cmp(valueReg, scratch2Reg); + + final done = Label(); + Label slowPath = addSlowPath(() { + if (hasNonTrivialInitializer(field.astField)) { + _callFunction( + functionRegistry.getFunction(field.astField, isInitializer: true), + ); + assert(valueReg == returnReg); + _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); + + if (field.isLate && field.isFinal) { + final ok = Label(); + _asm.ldr(scratch2Reg, RegOffsetAddress(scratch1Reg, 0)); + _asm.loadFromPool(tempReg, SentinelConstant()); + _asm.cmp(scratch2Reg, tempReg); + _asm.b(ok, .equal); + _asm.unimplemented( + 'Unimplemented: already initialized late final field in LoadStaticField', + ); + _asm.bind(ok); + } + + _asm.str(valueReg, RegOffsetAddress(scratch1Reg, 0)); + _asm.b(done); + } else { + _asm.unimplemented( + 'Unimplemented: uninitialized late field without initializer in LoadStaticField', + ); + } + }); + + _asm.b(slowPath, .equal); + _asm.bind(done); + } } @override void visitStoreStaticField(StoreStaticField instr) { - _asm.unimplemented('Unimplemented: code generation for StoreStaticField'); + final field = instr.field; + final valueReg = inputReg(instr, 0); + final scratch1Reg = temporaryReg(instr, 0); + final scratch2Reg = temporaryReg(instr, 1); + + // TODO: shared static fields + _loadStaticFieldAddress(scratch1Reg, field, scratch2Reg); + + if (instr.checkNotInitialized) { + _asm.ldr(scratch2Reg, RegOffsetAddress(scratch1Reg, 0)); + _asm.loadFromPool(tempReg, SentinelConstant()); + _asm.cmp(scratch2Reg, tempReg); + + final done = Label(); + Label slowPath = addSlowPath(() { + _asm.unimplemented( + 'Unimplemented: already initialized late final field in StoreStaticField', + ); + _asm.b(done); + }); + + _asm.b(slowPath, .notEqual); + _asm.bind(done); + } + + _asm.str(valueReg, RegOffsetAddress(scratch1Reg, 0)); } @override diff --git a/pkg/native_compiler/lib/back_end/arm64/constraints.dart b/pkg/native_compiler/lib/back_end/arm64/constraints.dart index 28eb72af2d7..16e88e0ff6a 100644 --- a/pkg/native_compiler/lib/back_end/arm64/constraints.dart +++ b/pkg/native_compiler/lib/back_end/arm64/constraints.dart @@ -2,12 +2,13 @@ // 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. +import 'package:cfg/ir/instructions.dart'; +import 'package:cfg/ir/types.dart'; import 'package:native_compiler/back_end/arm64/assembler.dart'; import 'package:native_compiler/back_end/arm64/stub_code_generator.dart'; import 'package:native_compiler/back_end/constraints.dart'; import 'package:native_compiler/back_end/locations.dart'; -import 'package:cfg/ir/instructions.dart'; -import 'package:cfg/ir/types.dart'; +import 'package:native_compiler/runtime/type_utils.dart'; /// Defines arm64 register allocation contraints for /// inputs/outputs/temporaries of the IR instructions. @@ -154,16 +155,24 @@ final class Arm64Constraints extends Constraints { @override InstructionConstraints? visitLoadStaticField(LoadStaticField instr) => - InstructionConstraints( - instr.field.type is DoubleType ? anyFpuRegister : anyCpuRegister, - const [], - ); + (instr.checkInitialized && hasNonTrivialInitializer(instr.field.astField)) + ? InstructionConstraints( + returnReg, + const [], + volatileRegistersExceptReturnReg, + ) + : const InstructionConstraints(anyCpuRegister, [], [ + anyCpuRegister, + anyCpuRegister, + ]); @override InstructionConstraints? visitStoreStaticField(StoreStaticField instr) => - InstructionConstraints(null, [ - instr.field.type is DoubleType ? anyFpuRegister : anyCpuRegister, - ]); + const InstructionConstraints( + null, + [anyCpuRegister], + [anyCpuRegister, anyCpuRegister], + ); @override InstructionConstraints? visitThrow(Throw instr) => InstructionConstraints( diff --git a/pkg/native_compiler/lib/back_end/object_pool.dart b/pkg/native_compiler/lib/back_end/object_pool.dart index 15705ecfce5..6f32073f98c 100644 --- a/pkg/native_compiler/lib/back_end/object_pool.dart +++ b/pkg/native_compiler/lib/back_end/object_pool.dart @@ -2,6 +2,7 @@ // 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. +import 'package:cfg/ir/field.dart'; import 'package:cfg/ir/functions.dart'; import 'package:kernel/ast.dart' as ast; @@ -95,3 +96,17 @@ final class ReservedEntry extends SpecializedEntry { @override int get numReservedEntries => 0; } + +/// Object pool entry representing offset of the static field +/// relative to static field table. +final class StaticFieldOffset extends SpecializedEntry { + final CField field; + StaticFieldOffset(this.field); + + @override + int get hashCode => field.hashCode + 13; + + @override + bool operator ==(Object other) => + other is StaticFieldOffset && this.field == other.field; +} diff --git a/pkg/native_compiler/lib/configuration.dart b/pkg/native_compiler/lib/configuration.dart index e27f2c26d1d..f5862f911f2 100644 --- a/pkg/native_compiler/lib/configuration.dart +++ b/pkg/native_compiler/lib/configuration.dart @@ -76,10 +76,12 @@ abstract base class Configuration { TargetCPU.arm64 => Arm64StackFrame(function), }; - CodeGenerator createCodeGenerator(BackEndState backEndState) => - switch (targetCPU) { - TargetCPU.arm64 => Arm64CodeGenerator(backEndState), - }; + CodeGenerator createCodeGenerator( + BackEndState backEndState, + FunctionRegistry functionRegistry, + ) => switch (targetCPU) { + TargetCPU.arm64 => Arm64CodeGenerator(backEndState, functionRegistry), + }; StubFactory createStubFactory(CodeConsumer consumeGeneratedCode) => switch (targetCPU) { @@ -136,7 +138,7 @@ final class DevelopmentCompilerConfiguration extends Configuration { ReorderBlocks(backEndState), LinearScanRegisterAllocator(backEndState, constraints), RegisterAllocationChecker(backEndState, constraints), - createCodeGenerator(backEndState), + createCodeGenerator(backEndState, functionRegistry), ]); } } diff --git a/pkg/native_compiler/lib/snapshot/snapshot.dart b/pkg/native_compiler/lib/snapshot/snapshot.dart index 334c6b594b4..d8c0be7632a 100644 --- a/pkg/native_compiler/lib/snapshot/snapshot.dart +++ b/pkg/native_compiler/lib/snapshot/snapshot.dart @@ -99,7 +99,12 @@ enum FunctionKind { /// /// This enum should match ModuleSnapshot::ObjectPoolEntryKind /// enum declared in runtime/vm/module_snapshot.cc. -enum ObjectPoolEntryKind { objectRef, newObjectTags, interfaceCall } +enum ObjectPoolEntryKind { + objectRef, + newObjectTags, + staticFieldOffset, + interfaceCall, +} abstract base class SerializationCluster { /// Add [object] to the cluster and push its outgoing references. @@ -145,6 +150,7 @@ class SnapshotSerializer { addBaseObject(null); addBaseObject(true); addBaseObject(false); + addBaseObject(SentinelConstant()); addBaseObject(const ast.DynamicType()); addBaseObject(const ast.VoidType()); addBaseObject(const ast.NullType()); @@ -1289,6 +1295,8 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { switch (entry) { case NewObjectTags(): serializer.push(entry.cls); + case StaticFieldOffset(): + serializer.push(entry.field); case InterfaceCallEntry(): // TODO: call through monomorphic/table dispatcher. final icData = icDatas[entry] = ICData( @@ -1301,6 +1309,7 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { ); serializer.push(icData); case ReservedEntry(): + break; } } else { serializer.push(entry); @@ -1332,6 +1341,9 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { case NewObjectTags(): serializer.writeUint(ObjectPoolEntryKind.newObjectTags.index); serializer.writeRefId(entry.cls); + case StaticFieldOffset(): + serializer.writeUint(ObjectPoolEntryKind.staticFieldOffset.index); + serializer.writeRefId(entry.field); case InterfaceCallEntry(): serializer.writeUint(ObjectPoolEntryKind.interfaceCall.index); serializer.writeRefId(icDatas[entry]); diff --git a/runtime/vm/module_snapshot.cc b/runtime/vm/module_snapshot.cc index d104587005e..0867cd7fc31 100644 --- a/runtime/vm/module_snapshot.cc +++ b/runtime/vm/module_snapshot.cc @@ -96,6 +96,7 @@ class ModuleSnapshot : public AllStatic { enum ObjectPoolEntryKind { kObjectRef, kNewObjectTags, + kStaticFieldOffset, kInterfaceCall, }; }; @@ -1172,6 +1173,14 @@ class ObjectPoolDeserializationCluster : public DeserializationCluster { kCompressedWordSize)); break; } + case ModuleSnapshot::kStaticFieldOffset: { + FieldPtr field = static_cast(d.ReadRef()); + pool->untag()->entry_bits()[j] = immediate_entry_bits; + UntaggedObjectPool::Entry& entry = pool->untag()->data()[j]; + entry.raw_value_ = FieldTable::FieldOffsetFor( + Smi::Value(field->untag()->host_offset_or_field_id())); + break; + } case ModuleSnapshot::kInterfaceCall: { pool->untag()->entry_bits()[j] = tagged_entry_bits; UntaggedObjectPool::Entry& entry = pool->untag()->data()[j]; @@ -1371,6 +1380,7 @@ void Deserializer::Deserialize() { AddBaseObject(Object::null_object()); AddBaseObject(Bool::True()); AddBaseObject(Bool::False()); + AddBaseObject(Object::sentinel()); AddBaseObject(Object::dynamic_type()); AddBaseObject(Object::void_type()); AddBaseObject(Type::Handle(zone(), object_store->null_type()));