| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // ignore_for_file: non_constant_identifier_names | |
| import 'package:cfg/ir/field.dart'; | |
| import 'package:cfg/ir/global_context.dart'; | |
| import 'package:cfg/utils/misc.dart'; | |
| import 'package:kernel/ast.dart' as ast; | |
| import 'package:kernel/core_types.dart' show CoreTypes; | |
| import 'package:kernel/library_index.dart' show LibraryIndex; | |
| import 'package:native_compiler/runtime/vm_defs.dart'; | |
| /// Memory access order for fields accessed concurrently. | |
| enum MemoryOrder { | |
| /// No ordering constraints. | |
| relaxed, | |
| /// Load performs acquire and store performs release. | |
| acquireRelease, | |
| } | |
| /// Computes layout of Dart objects (field offsets and instance size). | |
| class ObjectLayout { | |
| final VMOffsets vmOffsets; | |
| final int wordSize; | |
| final int compressedWordSize; | |
| /// Instance size before rounding. | |
| final Map<ast.Class, int> _instanceSize = {}; | |
| /// Name of the implicit type arguments field. | |
| late final ast.Name _typeArgumentsFieldName = ast.Name('#typeArguments'); | |
| /// Implicit type arguments field for a class. | |
| final Map<ast.Class, CField> _typeArgumentsField = {}; | |
| /// Field offsets. | |
| final Map<CField, int> _fieldOffset = {}; | |
| /// Fields stored as unboxed values. | |
| final Set<CField> _unboxedFields = {}; | |
| /// Field memory order (if not relaxed). | |
| final Map<CField, MemoryOrder> _fieldMemoryOrder = {}; | |
| ObjectLayout( | |
| this.vmOffsets, { | |
| required this.wordSize, | |
| required this.compressedWordSize, | |
| }); | |
| /// Return true if [value] can be represented as a Smi (small integer). | |
| bool isSmi(int value) { | |
| final shiftedOut = value >> smiBits(compressedWordSize); | |
| return shiftedOut == 0 || shiftedOut == -1; | |
| } | |
| int getUnalignedInstanceSize(ast.Class cls) { | |
| _ensureComputed(cls); | |
| return _instanceSize[cls]!; | |
| } | |
| int getInstanceSize(ast.Class cls) { | |
| return roundUp(getUnalignedInstanceSize(cls), objectAlignment(wordSize)); | |
| } | |
| int getFieldOffset(CField field) { | |
| assert(!field.isStatic); | |
| if (field.isSynthetic) { | |
| 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); | |
| return _fieldOffset[field]!; | |
| } | |
| bool isUnboxedField(CField field) { | |
| // TODO: support unboxed Dart fields. | |
| return _unboxedFields.contains(field); | |
| } | |
| MemoryOrder getFieldMemoryOrder(CField field) { | |
| return _fieldMemoryOrder[field] ?? MemoryOrder.relaxed; | |
| } | |
| CField? getTypeArgumentsField(ast.Class cls) { | |
| _ensureComputed(cls); | |
| return _typeArgumentsField[cls]; | |
| } | |
| bool _isComputed(ast.Class cls) => _instanceSize.containsKey(cls); | |
| void _ensureComputed(ast.Class cls) { | |
| if (!_isComputed(cls)) { | |
| _computeLayout(cls); | |
| } | |
| } | |
| void _computeLayout(ast.Class cls) { | |
| if (_computeLayoutOfBuiltInClass(cls)) { | |
| return; | |
| } | |
| final superclass = cls.superclass; | |
| if (superclass != null) { | |
| _ensureComputed(superclass); | |
| } | |
| int nextOffset = _instanceSize[superclass]!; | |
| final superTypeArgs = superclass != null | |
| ? _typeArgumentsField[superclass] | |
| : null; | |
| if (superTypeArgs != null) { | |
| // Inherit type arguments field from generic superclass. | |
| _typeArgumentsField[cls] = superTypeArgs; | |
| } else if (cls.typeParameters.isNotEmpty) { | |
| // This class is generic but superclass is not, so | |
| // introduce a new implicit type arguments field. | |
| final typeArgs = _createTypeArgumentsField(cls); | |
| _fieldOffset[typeArgs] = nextOffset; | |
| nextOffset += compressedWordSize; | |
| } | |
| for (final field in cls.fields) { | |
| if (!field.isStatic) { | |
| _fieldOffset[CField(field)] = nextOffset; | |
| nextOffset += compressedWordSize; | |
| } | |
| } | |
| _instanceSize[cls] = nextOffset; | |
| } | |
| CField _createTypeArgumentsField(ast.Class cls) { | |
| final field = CField( | |
| ast.Field.immutable( | |
| _typeArgumentsFieldName, | |
| isFinal: true, | |
| isStatic: false, | |
| fileUri: ast.dummyUri, | |
| )..parent = cls, | |
| ); | |
| _typeArgumentsField[cls] = field; | |
| return field; | |
| } | |
| CField _createBuiltInField( | |
| ast.Class cls, | |
| String name, | |
| ast.DartType type, | |
| int? offset, { | |
| bool isFinal = false, | |
| bool isUnboxed = false, | |
| MemoryOrder memoryOrder = .relaxed, | |
| }) { | |
| final fieldNode = isFinal | |
| ? ast.Field.immutable(ast.Name(name), type: type, fileUri: ast.dummyUri) | |
| : ast.Field.mutable(ast.Name(name), type: type, fileUri: ast.dummyUri); | |
| fieldNode.parent = cls; | |
| final field = CField(fieldNode); | |
| if (offset != null) { | |
| _fieldOffset[field] = offset; | |
| } | |
| if (isUnboxed) { | |
| _unboxedFields.add(field); | |
| } | |
| if (memoryOrder != .relaxed) { | |
| _fieldMemoryOrder[field] = memoryOrder; | |
| } | |
| return field; | |
| } | |
| late final CoreTypes _coreTypes = GlobalContext.instance.coreTypes; | |
| late final LibraryIndex _libraryIndex = GlobalContext.instance.coreLibraries; | |
| late final ast.Class _stringBaseClass = _libraryIndex.getClass( | |
| 'dart:core', | |
| '_StringBase', | |
| ); | |
| late final ast.Class _arrayClass = _libraryIndex.getClass( | |
| 'dart:core', | |
| '_Array', | |
| ); | |
| late final ast.Class _growableListClass = _libraryIndex.getClass( | |
| 'dart:core', | |
| '_GrowableList', | |
| ); | |
| late final ast.Class _linkedHashBaseClass = _libraryIndex.getClass( | |
| 'dart:_compact_hash', | |
| '_LinkedHashBase', | |
| ); | |
| late final ast.Class _linkedHashImmutableBaseClass = _libraryIndex.getClass( | |
| 'dart:_compact_hash', | |
| '_LinkedHashImmutableBase', | |
| ); | |
| late final ast.Class _suspendStateClass = _libraryIndex.getClass( | |
| 'dart:async', | |
| '_SuspendState', | |
| ); | |
| late final ast.Class _rawReceivePortClass = _libraryIndex.getClass( | |
| 'dart:isolate', | |
| '_RawReceivePort', | |
| ); | |
| late final ast.Class _sendPortClass = _libraryIndex.getClass( | |
| 'dart:isolate', | |
| 'SendPort', | |
| ); | |
| // Synthetic common base class for all typed data lists and ffi Pointer. | |
| late final ast.Class _pointerBaseClass = ast.Class( | |
| name: '#PointerBase', | |
| supertype: ast.Supertype(_coreTypes.objectClass, ast.DartTypeList.empty), | |
| fileUri: ast.dummyUri, | |
| )..parent = _coreTypes.coreLibrary; | |
| late final ast.Class _typedListBaseClass = _libraryIndex.getClass( | |
| 'dart:typed_data', | |
| '_TypedListBase', | |
| ); | |
| late final ast.Class _typedListClass = _libraryIndex.getClass( | |
| 'dart:typed_data', | |
| '_TypedList', | |
| ); | |
| late final ast.Class _typedListViewClass = _libraryIndex.getClass( | |
| 'dart:typed_data', | |
| '_TypedListView', | |
| ); | |
| late final ast.Class _uint32ListClass = _libraryIndex.getClass( | |
| 'dart:typed_data', | |
| 'Uint32List', | |
| ); | |
| // dart:core | |
| late final CField Object_classId = _createBuiltInField( | |
| _coreTypes.objectClass, | |
| '#classId', | |
| _coreTypes.intNonNullableRawType, | |
| null, // Bit field of tags field, implemented in code generator. | |
| isFinal: true, | |
| isUnboxed: true, | |
| ); | |
| late final CField StringBase_length = _createBuiltInField( | |
| _stringBaseClass, | |
| 'length', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.String_length_offset, | |
| isFinal: true, | |
| ); | |
| late final CField Array_length = _createBuiltInField( | |
| _arrayClass, | |
| 'length', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.Array_length_offset, | |
| isFinal: true, | |
| ); | |
| late final CField GrowableList_length = _createBuiltInField( | |
| _growableListClass, | |
| 'length', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.GrowableObjectArray_length_offset, | |
| ); | |
| late final CField GrowableList_data = _createBuiltInField( | |
| _growableListClass, | |
| 'data', | |
| _coreTypes.nonNullableRawType(_arrayClass), | |
| vmOffsets.GrowableObjectArray_data_offset, | |
| ); | |
| // dart:_compact_hash | |
| late final CField LinkedHashBase_index = _createBuiltInField( | |
| _linkedHashBaseClass, | |
| 'index', | |
| _coreTypes.nonNullableRawType(_uint32ListClass), | |
| vmOffsets.LinkedHashBase_index_offset, | |
| ); | |
| late final CField LinkedHashBase_hashMask = _createBuiltInField( | |
| _linkedHashBaseClass, | |
| 'hashMask', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.LinkedHashBase_hash_mask_offset, | |
| ); | |
| late final CField LinkedHashBase_data = _createBuiltInField( | |
| _linkedHashBaseClass, | |
| 'data', | |
| _coreTypes.listNonNullableRawType, | |
| vmOffsets.LinkedHashBase_data_offset, | |
| ); | |
| late final CField LinkedHashBase_usedData = _createBuiltInField( | |
| _linkedHashBaseClass, | |
| 'usedData', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.LinkedHashBase_used_data_offset, | |
| ); | |
| late final CField LinkedHashBase_deletedKeys = _createBuiltInField( | |
| _linkedHashBaseClass, | |
| 'deletedKeys', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.LinkedHashBase_deleted_keys_offset, | |
| ); | |
| late final CField LinkedHashImmutableBase_index = _createBuiltInField( | |
| _linkedHashImmutableBaseClass, | |
| 'index', | |
| _coreTypes.nullableRawType(_uint32ListClass), | |
| vmOffsets.LinkedHashBase_index_offset, | |
| memoryOrder: .acquireRelease, | |
| ); | |
| // dart:async | |
| late final CField SuspendState_functionData = _createBuiltInField( | |
| _suspendStateClass, | |
| 'functionData', | |
| _coreTypes.objectNonNullableRawType, | |
| vmOffsets.SuspendState_function_data_offset, | |
| ); | |
| late final CField SuspendState_thenCallback = _createBuiltInField( | |
| _suspendStateClass, | |
| 'thenCallback', | |
| ast.FunctionType( | |
| ast.DartTypeList.dynamic1, | |
| const ast.VoidType(), | |
| .nullable, | |
| ), | |
| vmOffsets.SuspendState_then_callback_offset, | |
| ); | |
| late final CField SuspendState_errorCallback = _createBuiltInField( | |
| _suspendStateClass, | |
| 'errorCallback', | |
| ast.FunctionType( | |
| ast.DartTypeList( | |
| _coreTypes.objectNonNullableRawType, | |
| _coreTypes.stackTraceNonNullableRawType, | |
| ), | |
| const ast.DynamicType(), | |
| .nullable, | |
| ), | |
| vmOffsets.SuspendState_error_callback_offset, | |
| ); | |
| // dart:isolate | |
| late final CField RawReceivePort_handler = _createBuiltInField( | |
| _rawReceivePortClass, | |
| 'handler', | |
| _coreTypes.functionNullableRawType, | |
| vmOffsets.ReceivePort_handler_offset, | |
| ); | |
| late final CField RawReceivePort_sendPort = _createBuiltInField( | |
| _rawReceivePortClass, | |
| 'sendPort', | |
| _coreTypes.nonNullableRawType(_sendPortClass), | |
| vmOffsets.ReceivePort_send_port_offset, | |
| isFinal: true, | |
| ); | |
| // dart:typed_data | |
| late final CField PointerBase_data = _createBuiltInField( | |
| _pointerBaseClass, | |
| 'data', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.PointerBase_data_offset, | |
| isFinal: true, | |
| isUnboxed: true, | |
| ); | |
| late final CField TypedListBase_length = _createBuiltInField( | |
| _typedListBaseClass, | |
| 'length', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.TypedDataBase_length_offset, | |
| isFinal: true, | |
| ); | |
| late final CField TypedListView_typedData = _createBuiltInField( | |
| _typedListViewClass, | |
| 'typedData', | |
| _coreTypes.nonNullableRawType(_typedListClass), | |
| vmOffsets.TypedDataView_typed_data_offset, | |
| isFinal: true, | |
| ); | |
| late final CField TypedListView_offsetInBytes = _createBuiltInField( | |
| _typedListViewClass, | |
| 'offsetInBytes', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.TypedDataView_offset_in_bytes_offset, | |
| isFinal: true, | |
| ); | |
| // External non-Dart fields. | |
| late final ast.Class _threadClass = ast.Class( | |
| name: '#Thread', | |
| supertype: ast.Supertype(_coreTypes.objectClass, ast.DartTypeList.empty), | |
| fileUri: ast.dummyUri, | |
| )..parent = _vmLibrary; | |
| late final CField Thread_threadLocals = _createBuiltInField( | |
| _threadClass, | |
| 'threadLocals', | |
| _coreTypes.listNonNullableRawType, | |
| vmOffsets.Thread_thread_locals_offset, | |
| ); | |
| late final CField Thread_nextTaskId = _createBuiltInField( | |
| _threadClass, | |
| 'nextTaskId', | |
| _coreTypes.intNonNullableRawType, | |
| vmOffsets.Thread_next_task_id_offset, | |
| isUnboxed: true, | |
| ); | |
| late final CField Thread_predefined_symbols_address = _createBuiltInField( | |
| _threadClass, | |
| 'predefinedSymbolsAddress', | |
| _coreTypes.intNonNullableRawType, // Address. | |
| vmOffsets.Thread_predefined_symbols_address_offset, | |
| isUnboxed: true, | |
| ); | |
| // Layout of built-in instances is specified either as | |
| // 'int size' or '(int size, int typeArgsOffset)' if class is generic. | |
| late final Map<String, Object> _dartCoreInstanceLayout = { | |
| '_Double': vmOffsets.Double_InstanceSize, | |
| '_GrowableList': ( | |
| vmOffsets.GrowableObjectArray_InstanceSize, | |
| vmOffsets.GrowableObjectArray_type_arguments_offset, | |
| ), | |
| '_Mint': vmOffsets.Mint_InstanceSize, | |
| '_WeakProperty': vmOffsets.WeakProperty_InstanceSize, | |
| '_WeakReference': ( | |
| vmOffsets.WeakReference_InstanceSize, | |
| vmOffsets.WeakReference_type_arguments_offset, | |
| ), | |
| 'Object': vmOffsets.Instance_InstanceSize, | |
| }; | |
| late final Map<String, Object> _dartTypedDataInstanceLayout = { | |
| '_Int32x4': vmOffsets.Int32x4_InstanceSize, | |
| '_Float32x4': vmOffsets.Float32x4_InstanceSize, | |
| '_Float64x2': vmOffsets.Float64x2_InstanceSize, | |
| '_TypedListView': vmOffsets.TypedDataView_InstanceSize, | |
| '_ByteDataView': vmOffsets.TypedDataView_InstanceSize, | |
| // TODO: add other built-in classes from dart:typed_data | |
| }; | |
| late final Map<String, Object> _dartCompactHashInstanceLayout = { | |
| '_LinkedHashBase': ( | |
| vmOffsets.LinkedHashBase_InstanceSize, | |
| vmOffsets.LinkedHashBase_type_arguments_offset, | |
| ), | |
| }; | |
| late final Map<String, Object> _dartNativewrappersInstanceLayout = { | |
| 'NativeFieldWrapperClass1': | |
| vmOffsets.Instance_InstanceSize + compressedWordSize, | |
| 'NativeFieldWrapperClass2': | |
| vmOffsets.Instance_InstanceSize + compressedWordSize, | |
| 'NativeFieldWrapperClass3': | |
| vmOffsets.Instance_InstanceSize + compressedWordSize, | |
| 'NativeFieldWrapperClass4': | |
| vmOffsets.Instance_InstanceSize + compressedWordSize, | |
| }; | |
| late final Map<String, Object> _dartVmInstanceLayout = {'#Thread': 0}; | |
| late final ast.Library _typedDataLibrary = _libraryIndex.getLibrary( | |
| 'dart:typed_data', | |
| ); | |
| late final ast.Library _compactHashLibrary = _libraryIndex.getLibrary( | |
| 'dart:_compact_hash', | |
| ); | |
| late final ast.Library _nativewrappersLibrary = _libraryIndex.getLibrary( | |
| 'dart:nativewrappers', | |
| ); | |
| late final ast.Library _vmLibrary = _libraryIndex.getLibrary('dart:_vm'); | |
| bool _computeLayoutOfBuiltInClass(ast.Class cls) { | |
| final library = cls.enclosingLibrary; | |
| if (!library.importUri.isScheme('dart')) { | |
| return false; | |
| } | |
| Object? layout; | |
| if (library == _coreTypes.coreLibrary) { | |
| layout = _dartCoreInstanceLayout[cls.name]; | |
| } else if (library == _typedDataLibrary) { | |
| layout = _dartTypedDataInstanceLayout[cls.name]; | |
| } else if (library == _compactHashLibrary) { | |
| layout = _dartCompactHashInstanceLayout[cls.name]; | |
| } else if (library == _nativewrappersLibrary) { | |
| layout = _dartNativewrappersInstanceLayout[cls.name]; | |
| } else if (library == _vmLibrary) { | |
| layout = _dartVmInstanceLayout[cls.name]; | |
| } | |
| // TODO: add built-in classes from dart:ffi | |
| if (layout != null) { | |
| switch (layout) { | |
| case int(): | |
| _instanceSize[cls] = layout; | |
| break; | |
| case (int size, int typeArgsOffset): | |
| _instanceSize[cls] = size; | |
| _fieldOffset[_createTypeArgumentsField(cls)] = typeArgsOffset; | |
| break; | |
| default: | |
| throw 'Unexpected built-in class layout ${layout.runtimeType} $layout'; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| } |