blob: 1d1b95d54b240a4283f0602e578e7a301a04ffa1 [file]
// 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.
import 'dart:convert';
import 'dart:typed_data';
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;
import 'package:kernel/type_environment.dart'
as ast_type_environment
show StaticTypeContext;
import 'package:native_compiler/back_end/code.dart';
import 'package:native_compiler/back_end/code_metadata.dart';
import 'package:native_compiler/back_end/object_pool.dart';
import 'package:native_compiler/configuration.dart';
import 'package:native_compiler/runtime/names.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/type_utils.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
/// Kinds of Dart snapshots.
/// Should match Snapshot::Kind enum in runtime/vm/snapshot.h.
enum SnapshotKind { full, fullJIT, fullAOT, module, invalid }
/// Dart snapshot constants.
class Snapshot {
// Dart snapshot magic and header format.
// Values of these constants should match corresponding constants
// declared in Snapshot class in runtime/vm/snapshot.h.
static const int magicValue = 0xdcdcf5f5;
static const int magicOffset = 0;
static const int lengthOffset = magicOffset + 4;
static const int kindOffset = lengthOffset + 8;
static const int headerSize = kindOffset + 8;
/// Endianness for writing snapshots.
/// All our targets are little-endian.
static const Endian endianness = Endian.little;
/// Version of module snapshot format.
/// Should match ModuleSnapshot::kFormatVersion
/// constant declared in runtime/vm/module_snapshot.cc.
static const int moduleSnapshotFormatVersion = 1;
}
/// Predefined clusters in the module snapshots.
///
/// This enum should match ModuleSnapshot::PredefinedClusters
/// enum declared in runtime/vm/module_snapshot.cc.
enum PredefinedClusters {
oneByteStrings,
twoByteStrings,
libraryRefs,
privateNames,
classRefs,
fieldRefs,
functionRefs,
closureFunctionRefs,
closureRefs,
argumentsDescriptorRefs,
recordShapeRefs,
ints,
doubles,
lists,
maps,
sets,
records,
instantiatedClosures,
typeParameters,
interfaceTypes,
functionTypes,
recordTypes,
typeParameterTypes,
typeArguments,
codes,
icDatas,
subtypeTestCaches,
objectPools,
exceptionHandlers,
pcDescriptors,
catchEntryMoves,
codeSourceMap,
instances, // Separate cluster for every class.
}
/// Function kinds in the module snapshots.
///
/// This enum should match ModuleSnapshot::FunctionKind
/// enum declared in runtime/vm/module_snapshot.cc.
enum FunctionKind {
regular,
getter,
setter,
generativeConstructor,
factoryConstructor,
implicitGetter,
implicitSetter,
fieldInitializer,
methodExtractor,
}
/// Object pool entry kinds in the module snapshots.
///
/// This enum should match ModuleSnapshot::ObjectPoolEntryKind
/// enum declared in runtime/vm/module_snapshot.cc.
enum ObjectPoolEntryKind {
objectRef,
newObjectTags,
staticFieldOffset,
interfaceCall,
dynamicCall,
unboxedInt,
unboxedDouble,
}
abstract base class SerializationCluster {
/// Add [object] to the cluster and push its outgoing references.
void trace(SnapshotSerializer serializer, Object object);
/// Write the cluster type and information needed to
/// reference imported objects.
void writePreLoad(SnapshotSerializer serializer);
/// Write information needed to allocate the cluster's objects.
void writeAlloc(SnapshotSerializer serializer) {}
/// Write contents of the cluster's objects.
void writeFill(SnapshotSerializer serializer) {}
/// Write information needed to post-process
/// allocated objects (e.g. perform canonicalization).
void writePostLoad(SnapshotSerializer serializer) {}
}
class SnapshotSerializer {
// Discovered but not allocated yet.
static const int unallocatedReference = -1;
// The first allocated reference.
static const int firstReference = 1;
final TargetCPU targetCPU;
final FunctionRegistry functionRegistry;
final ObjectLayout objectLayout;
int numBaseObjects = 0;
int numObjects = 0;
final List<Object?> _objects = [];
final Map<Object?, int> _objectIds = {};
final List<Object?> _stack = [];
final List<SerializationCluster?> _clusters = List.filled(
PredefinedClusters.values.length,
null,
);
final Map<ast.Class, SerializationCluster> _instanceClusters = {};
final SnapshotStreamWriter out = SnapshotStreamWriter();
SnapshotSerializer(this.targetCPU, this.functionRegistry, this.objectLayout) {
addBaseObject(null);
addBaseObject(true);
addBaseObject(false);
addBaseObject(SentinelConstant());
addBaseObject(const ast.DynamicType());
addBaseObject(const ast.VoidType());
addBaseObject(const ast.NullType());
addBaseObject(const ast.NeverType.nonNullable());
addBaseObject(ast.ListConstant(const ast.DynamicType(), const []));
addBaseObject(UndefinedConstant());
addBaseObject(ExceptionHandlers(hasAsyncHandler: false));
addBaseObject(ExceptionHandlers(hasAsyncHandler: true));
addBaseObject(PcDescriptors());
// TODO: generate these stubs instead of referencing them from the VM.
addBaseObject(StubCode.Subtype1TestCache);
addBaseObject(StubCode.Subtype2TestCache);
addBaseObject(StubCode.Subtype3TestCache);
addBaseObject(StubCode.Subtype4TestCache);
addBaseObject(StubCode.Subtype6TestCache);
addBaseObject(StubCode.InstantiateTypeArguments);
addBaseObject(StubCode.InitAsync);
addBaseObject(StubCode.InitAsyncStar);
addBaseObject(StubCode.InitSyncStar);
addBaseObject(StubCode.Await);
addBaseObject(StubCode.AwaitWithTypeCheck);
addBaseObject(StubCode.YieldAsyncStar);
addBaseObject(StubCode.SuspendSyncStarAtStart);
addBaseObject(StubCode.SuspendSyncStarAtYield);
addBaseObject(StubCode.ReturnAsync);
addBaseObject(StubCode.ReturnAsyncNotFuture);
addBaseObject(StubCode.ReturnAsyncStar);
numObjects = numBaseObjects;
}
void writeModuleSnapshot() {
out.reserve(Snapshot.headerSize);
writeVersionAndFeatures();
out.writeUint(numBaseObjects);
out.writeUint(numObjects);
final codeCluster = getPredefinedCluster(
PredefinedClusters.codes,
) as CodeSerializationCluster;
final lastCode = codeCluster._objects.last;
out.writeUint(
lastCode.instructionsImageOffset! + lastCode.instructions.lengthInBytes,
);
final clusters = [
for (final c in _clusters) ?c,
..._instanceClusters.values,
];
out.writeUint(clusters.length);
for (final cluster in clusters) {
cluster.writePreLoad(this);
}
for (final cluster in clusters) {
cluster.writeAlloc(this);
}
assert(_objects.length == numObjects);
for (final cluster in clusters) {
cluster.writeFill(this);
}
for (final cluster in clusters) {
cluster.writePostLoad(this);
}
fillHeader(SnapshotKind.module);
}
void writeVersionAndFeatures() {
out.writeUint(Snapshot.moduleSnapshotFormatVersion);
out.writeUint8List(utf8.encode(targetCPU.name));
out.writeByte(0);
}
void fillHeader(SnapshotKind kind) {
out.writeInt32At(Snapshot.magicOffset, Snapshot.magicValue);
out.writeInt64At(Snapshot.lengthOffset, out.position);
out.writeInt64At(Snapshot.kindOffset, kind.index);
}
void addBaseObject(Object? obj) {
assignRef(obj);
++numBaseObjects;
}
void addRoot(Object obj) {
push(obj);
while (_stack.isNotEmpty) {
trace(_stack.removeLast()!);
}
}
Object? preprocess(Object? obj) => switch (obj) {
ast.Name() => Name(obj.text, obj.library),
ast.PrimitiveConstant() when obj is! ast.DoubleConstant => obj.value,
ast.TypeLiteralConstant() => preprocess(obj.type),
ast.SymbolConstant() => ast.InstanceConstant(
GlobalContext.instance.coreTypes.internalSymbolClass.reference,
const [],
{
GlobalContext.instance.coreTypes.index
.getField('dart:_internal', 'Symbol', '_name')
.fieldReference: getNameConstant(
obj.name,
obj.libraryReference?.asLibrary,
),
},
),
ast.FutureOrType() => ast.InterfaceType(
GlobalContext.instance.coreTypes.deprecatedFutureOrClass,
obj.declaredNullability,
[obj.typeArgument],
),
ast.ExtensionType() => preprocess(obj.extensionTypeErasure),
ast.NeverType() when obj.nullability == .nullable => const ast.NullType(),
TypeArgumentsConstant() when isAllDynamic(obj.types) => null,
WrapperConstant() => preprocess(obj.unwrap),
_ => obj,
};
void trace(Object obj) {
++numObjects;
final cluster = getCluster(obj);
cluster.trace(this, obj);
}
void push(Object? obj) {
obj = preprocess(obj);
_objectIds.putIfAbsent(obj, () {
_stack.add(obj);
return unallocatedReference;
});
}
int assignRef(Object? obj) {
assert(identical(preprocess(obj), obj));
final id = firstReference + _objects.length;
_objects.add(obj);
_objectIds[obj] = id;
return id;
}
void writeUint(int value) {
out.writeUint(value);
}
void writeRefId(Object? obj) {
obj = preprocess(obj);
final refId = _objectIds[obj]!;
assert(refId >= firstReference);
out.writeRefId(refId);
}
SerializationCluster getCluster(Object obj) => switch (obj) {
// Program structure.
ast.Library() => getPredefinedCluster(PredefinedClusters.libraryRefs),
ast.Class() => getPredefinedCluster(PredefinedClusters.classRefs),
ast.Field() => getPredefinedCluster(PredefinedClusters.fieldRefs),
ClosureFunction() => getPredefinedCluster(
PredefinedClusters.closureFunctionRefs,
),
CFunction() => getPredefinedCluster(PredefinedClusters.functionRefs),
PrivateName() => getPredefinedCluster(PredefinedClusters.privateNames),
ArgumentsShape() => getPredefinedCluster(
PredefinedClusters.argumentsDescriptorRefs,
),
RecordShape() => getPredefinedCluster(PredefinedClusters.recordShapeRefs),
// Constants.
String() => getPredefinedCluster(
OneByteStringSerializationCluster.isOneByteString(obj)
? PredefinedClusters.oneByteStrings
: PredefinedClusters.twoByteStrings,
),
int() => getPredefinedCluster(PredefinedClusters.ints),
ast.DoubleConstant() => getPredefinedCluster(PredefinedClusters.doubles),
ast.ListConstant() => getPredefinedCluster(PredefinedClusters.lists),
ast.MapConstant() => getPredefinedCluster(PredefinedClusters.maps),
ast.SetConstant() => getPredefinedCluster(PredefinedClusters.sets),
ast.RecordConstant() => getPredefinedCluster(PredefinedClusters.records),
ast.InstanceConstant() => getInstanceCluster(obj.classNode),
ast.InstantiationConstant() => getPredefinedCluster(
PredefinedClusters.instantiatedClosures,
),
ast.TearOffConstant() => getPredefinedCluster(
PredefinedClusters.closureRefs,
),
// Types.
ast.InterfaceType() => getPredefinedCluster(
PredefinedClusters.interfaceTypes,
),
ast.FutureOrType() => getPredefinedCluster(
PredefinedClusters.interfaceTypes,
),
ast.FunctionType() => getPredefinedCluster(
PredefinedClusters.functionTypes,
),
ast.RecordType() => getPredefinedCluster(PredefinedClusters.recordTypes),
ast.TypeParameterType() => getPredefinedCluster(
PredefinedClusters.typeParameterTypes,
),
ast.StructuralParameterType() => getPredefinedCluster(
PredefinedClusters.typeParameterTypes,
),
TypeParameters() => getPredefinedCluster(PredefinedClusters.typeParameters),
TypeArgumentsConstant() => getPredefinedCluster(
PredefinedClusters.typeArguments,
),
// Generated code and object pool
Code() => getPredefinedCluster(PredefinedClusters.codes),
ICData() => getPredefinedCluster(PredefinedClusters.icDatas),
SubtypeTestCache() => getPredefinedCluster(
PredefinedClusters.subtypeTestCaches,
),
ObjectPool() => getPredefinedCluster(PredefinedClusters.objectPools),
ExceptionHandlers() => getPredefinedCluster(
PredefinedClusters.exceptionHandlers,
),
PcDescriptors() => getPredefinedCluster(PredefinedClusters.pcDescriptors),
CatchEntryMoves() => getPredefinedCluster(
PredefinedClusters.catchEntryMoves,
),
CodeSourceMap() => getPredefinedCluster(PredefinedClusters.codeSourceMap),
_ => throw 'Unxpected ${obj.runtimeType} $obj',
};
SerializationCluster getPredefinedCluster(PredefinedClusters clusterId) =>
(_clusters[clusterId.index] ??= _createPredefinedCluster(clusterId));
SerializationCluster getInstanceCluster(ast.Class cls) =>
(_instanceClusters[cls] ??= InstanceSerializationCluster(cls));
SerializationCluster _createPredefinedCluster(PredefinedClusters clusterId) =>
switch (clusterId) {
.libraryRefs => LibraryRefSerializationCluster(),
.classRefs => ClassRefSerializationCluster(),
.fieldRefs => FieldRefSerializationCluster(),
.functionRefs => FunctionRefSerializationCluster(),
.closureFunctionRefs => ClosureFunctionRefSerializationCluster(),
.closureRefs => ClosureRefSerializationCluster(),
.argumentsDescriptorRefs =>
ArgumentsDescriptorRefSerializationCluster(),
.recordShapeRefs => RecordShapeRefSerializationCluster(),
.oneByteStrings => OneByteStringSerializationCluster(),
.twoByteStrings => TwoByteStringSerializationCluster(),
.privateNames => PrivateNameSerializationCluster(),
.ints => IntSerializationCluster(),
.doubles => DoubleSerializationCluster(),
.lists => ListSerializationCluster(),
.maps => MapSerializationCluster(),
.sets => SetSerializationCluster(),
.records => RecordSerializationCluster(),
.instantiatedClosures => throw 'Unimplemented cluster $clusterId',
.typeParameters => TypeParametersSerializationCluster(),
.typeArguments => TypeArgumentsSerializationCluster(),
.interfaceTypes => InterfaceTypeSerializationCluster(),
.functionTypes => FunctionTypeSerializationCluster(),
.recordTypes => RecordTypeSerializationCluster(),
.typeParameterTypes => TypeParameterTypeSerializationCluster(),
.codes => CodeSerializationCluster(),
.icDatas => ICDataSerializationCluster(),
.subtypeTestCaches => SubtypeTestCacheSerializationCluster(),
.objectPools => ObjectPoolSerializationCluster(),
.exceptionHandlers => ExceptionHandlersSerializationCluster(),
.pcDescriptors => PcDescriptorsSerializationCluster(),
.catchEntryMoves => CatchEntryMovesSerializationCluster(),
.codeSourceMap => CodeSourceMapSerializationCluster(),
.instances => throw 'Each class has a separate instance cluster',
};
}
/// AST Constant which wraps an arbitrary object.
/// Used during snapshot serialization in order to embed arbitrary objects
/// (such as Name) into other constants (such as ListConstant).
class WrapperConstant(final Object? unwrap) extends ast.AuxiliaryConstant {
@override
void visitChildren(ast.Visitor v) => throw 'Should not be called.';
@override
void toTextInternal(ast_printer.AstPrinter printer) {
printer.write('WrapperConstant(${unwrap})');
}
@override
ast.DartType getType(ast_type_environment.StaticTypeContext context) =>
throw 'Should not be called.';
@override
int get hashCode => unwrap.hashCode;
@override
bool operator ==(Object other) {
return other is WrapperConstant && this.unwrap == other.unwrap;
}
}
/// Wrap given [name] into a Constant.
/// Private names should have a non-null [library].
ast.Constant getNameConstant(String name, ast.Library? library) =>
(library != null)
? WrapperConstant(PrivateName(name, library))
: ast.StringConstant(name);
/// Create a ListConstant from given [elements], wrapping them if needed.
ast.ListConstant getListConstant(List<Object?> elements) => ast.ListConstant(
const ast.DynamicType(),
[for (final e in elements) e is ast.Constant ? e : WrapperConstant(e)],
);
final class LibraryRefSerializationCluster extends SerializationCluster {
final List<ast.Library> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final library = object as ast.Library;
_objects.add(library);
serializer.push(library.importUri.toString());
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.libraryRefs.index);
serializer.writeUint(_objects.length);
for (final library in _objects) {
serializer.assignRef(library);
serializer.writeRefId(library.importUri.toString());
}
}
}
extension on ast.Class {
Object get mangledName =>
name.startsWith('_') ? PrivateName(name, enclosingLibrary) : name;
}
final class ClassRefSerializationCluster extends SerializationCluster {
final List<ast.Class> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final classNode = object as ast.Class;
_objects.add(classNode);
serializer.push(classNode.enclosingLibrary);
serializer.push(classNode.mangledName);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.classRefs.index);
serializer.writeUint(_objects.length);
for (final classNode in _objects) {
serializer.assignRef(classNode);
serializer.writeRefId(classNode.enclosingLibrary);
serializer.writeRefId(classNode.mangledName);
}
}
}
final class FieldRefSerializationCluster extends SerializationCluster {
final List<ast.Field> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final field = object as ast.Field;
_objects.add(field);
serializer.push(field.enclosingClass ?? field.enclosingLibrary);
serializer.push(field.name);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.fieldRefs.index);
serializer.writeUint(_objects.length);
for (final field in _objects) {
serializer.assignRef(field);
serializer.writeRefId(field.enclosingClass ?? field.enclosingLibrary);
serializer.writeRefId(field.name);
}
}
}
final class FunctionRefSerializationCluster extends SerializationCluster {
final List<CFunction> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final function = object as CFunction;
_objects.add(function);
switch (function) {
case RegularFunction() ||
GenerativeConstructor() ||
GetterFunction() ||
SetterFunction() ||
FieldInitializerFunction():
serializer.push(
function.member.enclosingClass ?? function.member.enclosingLibrary,
);
serializer.push(function.member.name);
break;
case ClosureFunction():
throw 'Unexpected ${function.runtimeType} in FunctionRefSerializationCluster';
}
;
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.functionRefs.index);
serializer.writeUint(_objects.length);
for (final function in _objects) {
serializer.assignRef(function);
final kind = switch (function) {
RegularFunction() =>
(function.member as ast.Procedure).isFactory
? FunctionKind.factoryConstructor
: FunctionKind.regular,
GenerativeConstructor() => FunctionKind.generativeConstructor,
ImplicitFieldGetter() => FunctionKind.implicitGetter,
ImplicitFieldSetter() => FunctionKind.implicitSetter,
FieldInitializerFunction() => FunctionKind.fieldInitializer,
MethodExtractor() => FunctionKind.methodExtractor,
GetterFunction() => FunctionKind.getter,
SetterFunction() => FunctionKind.setter,
ClosureFunction() =>
throw 'Unexpected ${function.runtimeType} in FunctionRefSerializationCluster',
};
serializer.writeUint(kind.index);
switch (function) {
case RegularFunction() ||
GenerativeConstructor() ||
GetterFunction() ||
SetterFunction() ||
FieldInitializerFunction():
serializer.writeRefId(
function.member.enclosingClass ?? function.member.enclosingLibrary,
);
serializer.writeRefId(function.member.name);
break;
case ClosureFunction():
throw 'Unexpected ${function.runtimeType} in FunctionRefSerializationCluster';
}
}
}
}
final class ClosureFunctionRefSerializationCluster
extends SerializationCluster {
final List<ClosureFunction> _objects = [];
CFunction _enclosingMemberFunction(
SnapshotSerializer serializer,
ClosureFunction function,
) {
final member = function.member;
return serializer.functionRegistry.getFunction(
member,
isGetter: member is ast.Procedure && member.isGetter,
isSetter: member is ast.Procedure && member.isSetter,
isInitializer: member is ast.Field,
);
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final function = object as ClosureFunction;
_objects.add(function);
serializer.push(_enclosingMemberFunction(serializer, function));
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.closureFunctionRefs.index);
serializer.writeUint(_objects.length);
for (final function in _objects) {
serializer.assignRef(function);
serializer.writeRefId(_enclosingMemberFunction(serializer, function));
switch (function) {
case TearOffFunction():
serializer.writeUint(0);
case LocalFunction():
final id = function.localFunction.id.toInt();
assert(id > 0);
serializer.writeUint(id);
}
}
}
}
final class ClosureRefSerializationCluster extends SerializationCluster {
final List<ast.TearOffConstant> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final closure = object as ast.TearOffConstant;
_objects.add(closure);
serializer.push(
serializer.functionRegistry.getFunction(closure.target, isTearOff: true),
);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.closureRefs.index);
serializer.writeUint(_objects.length);
for (final closure in _objects) {
serializer.assignRef(closure);
serializer.writeRefId(
serializer.functionRegistry.getFunction(
closure.target,
isTearOff: true,
),
);
}
}
}
final class ArgumentsDescriptorRefSerializationCluster
extends SerializationCluster {
final List<ArgumentsShape> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final args = object as ArgumentsShape;
_objects.add(args);
for (final name in args.named) {
serializer.push(name);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.argumentsDescriptorRefs.index);
serializer.writeUint(_objects.length);
for (final args in _objects) {
serializer.assignRef(args);
serializer.writeUint(args.types);
serializer.writeUint(args.positional);
serializer.writeUint(args.named.length);
for (final name in args.named) {
serializer.writeRefId(name);
}
}
}
}
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 = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final string = object as String;
_objects.add(string);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.oneByteStrings.index);
serializer.writeUint(_objects.length);
for (final string in _objects) {
serializer.assignRef(string);
serializer.writeUint(string.length);
serializer.out.writeUint8List(string.codeUnits);
}
}
static bool isOneByteString(String value) {
const maxLatin1 = 0xff;
for (var i = 0; i < value.length; ++i) {
if (value.codeUnitAt(i) > maxLatin1) {
return false;
}
}
return true;
}
}
final class TwoByteStringSerializationCluster extends SerializationCluster {
final List<String> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final string = object as String;
_objects.add(string);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.twoByteStrings.index);
serializer.writeUint(_objects.length);
for (final string in _objects) {
serializer.assignRef(string);
serializer.writeUint(string.length);
serializer.out.align(2);
serializer.out.writeUtf16String(string);
}
}
}
final class PrivateNameSerializationCluster extends SerializationCluster {
final List<PrivateName> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final name = object as PrivateName;
_objects.add(name);
serializer.push(name.library);
serializer.push(name.text);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.privateNames.index);
serializer.writeUint(_objects.length);
for (final name in _objects) {
serializer.assignRef(name);
serializer.writeRefId(name.library);
serializer.writeRefId(name.text);
}
}
}
final class IntSerializationCluster extends SerializationCluster {
final List<int> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final integer = object as int;
_objects.add(integer);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.ints.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
// Move Smi values to the beginning.
final objectLayout = serializer.objectLayout;
var i = 0;
for (var j = 0; j < _objects.length; ++j) {
final v = _objects[j];
if (objectLayout.isSmi(v)) {
final tmp = _objects[i];
_objects[i] = v;
_objects[j] = tmp;
++i;
}
}
serializer.writeUint(_objects.length);
serializer.writeUint(i);
for (final integer in _objects) {
serializer.assignRef(integer);
serializer.out.writeInt(integer);
}
}
}
final class DoubleSerializationCluster extends SerializationCluster {
final List<ast.DoubleConstant> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final dbl = object as ast.DoubleConstant;
_objects.add(dbl);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.doubles.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final dbl in _objects) {
serializer.assignRef(dbl);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final dbl in _objects) {
serializer.out.writeDouble(dbl.value);
}
}
}
/// Serialization cluster for constant lists.
///
/// On the VM, constant lists are represented with dart:core._ImmutableList
/// objects / Array handles with kImmutableArrayCid class id.
final class ListSerializationCluster extends SerializationCluster {
final List<ast.ListConstant> _objects = [];
Object? _typeArguments(ast.ListConstant list) {
final typeArg = list.typeArgument;
if (typeArg is ast.DynamicType) return null;
return TypeArgumentsConstant([typeArg]);
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final list = object as ast.ListConstant;
_objects.add(list);
serializer.push(_typeArguments(list));
for (final entry in list.entries) {
serializer.push(entry);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.lists.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final list in _objects) {
serializer.assignRef(list);
serializer.writeUint(list.entries.length);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final list in _objects) {
serializer.writeUint(list.entries.length);
serializer.writeRefId(_typeArguments(list));
for (final entry in list.entries) {
serializer.writeRefId(entry);
}
}
}
}
final class MapSerializationCluster extends SerializationCluster {
final List<ast.MapConstant> _objects = [];
final List<ast.ListConstant> _dataLists = [];
Object? _typeArguments(ast.MapConstant map) {
final keyType = map.keyType;
final valueType = map.valueType;
if (keyType is ast.DynamicType && valueType is ast.DynamicType) return null;
return TypeArgumentsConstant([keyType, valueType]);
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final map = object as ast.MapConstant;
final data = ast.ListConstant(const ast.DynamicType(), [
for (final entry in map.entries) ...[entry.key, entry.value],
]);
_objects.add(map);
_dataLists.add(data);
serializer.push(_typeArguments(map));
serializer.push(data);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.maps.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final map in _objects) {
serializer.assignRef(map);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0; i < _objects.length; ++i) {
final map = _objects[i];
final data = _dataLists[i];
serializer.writeRefId(_typeArguments(map));
serializer.writeRefId(data);
serializer.writeUint(map.entries.length << 1);
}
}
}
final class SetSerializationCluster extends SerializationCluster {
final List<ast.SetConstant> _objects = [];
final List<ast.ListConstant> _dataLists = [];
Object? _typeArguments(ast.SetConstant obj) {
final typeArg = obj.typeArgument;
if (typeArg is ast.DynamicType) return null;
return TypeArgumentsConstant([typeArg]);
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final obj = object as ast.SetConstant;
final data = ast.ListConstant(const ast.DynamicType(), obj.entries);
_objects.add(obj);
_dataLists.add(data);
serializer.push(_typeArguments(obj));
serializer.push(data);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.sets.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0; i < _objects.length; ++i) {
final obj = _objects[i];
final data = _dataLists[i];
serializer.writeRefId(_typeArguments(obj));
serializer.writeRefId(data);
serializer.writeUint(obj.entries.length);
}
}
}
/// 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 = [];
InstanceSerializationCluster(this._cls);
Object? _typeArguments(ast.InstanceConstant obj) {
final typeArgs = getInstantiatorTypeArguments(_cls, obj.typeArguments);
if (typeArgs == null) return null;
return TypeArgumentsConstant(typeArgs);
}
List<ast.Reference?> _computeFieldOrder(
SnapshotSerializer serializer,
CField? typeArgumentsField,
) {
final objectLayout = serializer.objectLayout;
final offsetToField = <int, ast.Field>{};
for (ast.Class? cls = _cls; cls != null; cls = cls.superclass) {
for (final field in cls.fields) {
if (field.isInstanceMember) {
offsetToField[objectLayout.getFieldOffset(CField(field))] = field;
}
}
}
final typeArgsOffset = (typeArgumentsField != null)
? objectLayout.getFieldOffset(typeArgumentsField)
: -1;
final compressedWordSize = objectLayout.compressedWordSize;
final firstOffset = objectLayout.vmOffsets.Instance_first_field_offset;
final size = objectLayout.getUnalignedInstanceSize(_cls);
// TODO: support unboxed fields
return [
for (
int offset = firstOffset;
offset < size;
offset += compressedWordSize
)
(offset == typeArgsOffset)
? null
: offsetToField[offset]!.fieldReference,
];
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final obj = object as ast.InstanceConstant;
assert(obj.classNode == _cls);
_objects.add(obj);
serializer.push(_cls);
serializer.push(_typeArguments(obj));
for (final fieldValue in obj.fieldValues.values) {
serializer.push(fieldValue);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.instances.index);
serializer.writeRefId(_cls);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
serializer.writeUint(
serializer.objectLayout.getUnalignedInstanceSize(_cls),
);
serializer.writeUint(serializer.objectLayout.getInstanceSize(_cls));
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
final typeArgumentsField = serializer.objectLayout.getTypeArgumentsField(
_cls,
);
final fields = _computeFieldOrder(serializer, typeArgumentsField);
for (final obj in _objects) {
for (final field in fields) {
if (field == null) {
serializer.writeRefId(_typeArguments(obj));
} else {
serializer.writeRefId(obj.fieldValues[field]!);
}
}
}
}
}
final class TypeArgumentsSerializationCluster extends SerializationCluster {
final List<TypeArgumentsConstant> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final typeArgs = object as TypeArgumentsConstant;
_objects.add(typeArgs);
for (final type in typeArgs.types) {
serializer.push(type);
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.typeArguments.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final typeArgs in _objects) {
serializer.assignRef(typeArgs);
serializer.writeUint(typeArgs.types.length);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final typeArgs in _objects) {
serializer.writeUint(typeArgs.types.length);
for (final type in typeArgs.types) {
serializer.writeRefId(type);
}
}
}
}
final class InterfaceTypeSerializationCluster extends SerializationCluster {
final List<ast.InterfaceType> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final type = object as ast.InterfaceType;
_objects.add(type);
serializer.push(type.classNode);
serializer.push(TypeArgumentsConstant(type.typeArguments));
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.interfaceTypes.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final type in _objects) {
serializer.assignRef(type);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final type in _objects) {
serializer.writeRefId(type.classNode);
serializer.writeUint(
type.declaredNullability == ast.Nullability.nullable ? 1 : 0,
);
serializer.writeRefId(TypeArgumentsConstant(type.typeArguments));
}
}
}
/// Declaration of type parameters, corresponds to the VM TypeParameters object.
class TypeParameters._(
final ast.ListConstant names,
final TypeArgumentsConstant bounds,
final TypeArgumentsConstant defaultTypes,
) {
factory fromStructuralParameters(List<ast.StructuralParameter> params) {
final names = getListConstant([for (final p in params) p.name!]);
final bounds = TypeArgumentsConstant([for (final p in params) p.bound]);
final defaultTypes = TypeArgumentsConstant([
for (final p in params) p.defaultType,
]);
return TypeParameters._(names, bounds, defaultTypes);
}
}
final class TypeParametersSerializationCluster extends SerializationCluster {
final List<TypeParameters> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final obj = object as TypeParameters;
_objects.add(obj);
serializer.push(obj.names);
serializer.push(obj.bounds);
serializer.push(obj.defaultTypes);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.typeParameters.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0; i < _objects.length; i++) {
final obj = _objects[i];
serializer.writeRefId(obj.names);
serializer.writeRefId(obj.bounds);
serializer.writeRefId(obj.defaultTypes);
}
}
}
final class FunctionTypeSerializationCluster extends SerializationCluster {
final List<ast.FunctionType> _objects = [];
final List<
({
TypeParameters? typeParameters,
ast.ListConstant parameterTypes,
ast.ListConstant parameterNames,
})
>
_fields = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final type = object as ast.FunctionType;
_objects.add(type);
if (type.typeParameters.isNotEmpty) {
// Establish StructuralParameter -> owner links.
final typeParamCluster = serializer.getPredefinedCluster(
PredefinedClusters.typeParameterTypes,
) as TypeParameterTypeSerializationCluster;
for (final tp in type.typeParameters) {
typeParamCluster._structuralParameterOwner[tp] = type;
}
}
final typeParameters = type.typeParameters.isNotEmpty
? TypeParameters.fromStructuralParameters(type.typeParameters)
: null;
final parameterTypes = getListConstant([
const ast.DynamicType(), // implicit closure parameter
...type.positionalParameters,
for (final np in type.namedParameters) np.type,
]);
// TODO: encode parameter flags in the names array
final parameterNames = getListConstant([
for (final np in type.namedParameters) np.name,
]);
serializer.push(typeParameters);
serializer.push(type.returnType);
serializer.push(parameterTypes);
serializer.push(parameterNames);
_fields.add((
typeParameters: typeParameters,
parameterTypes: parameterTypes,
parameterNames: parameterNames,
));
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.functionTypes.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; i < _objects.length; i++) {
final type = _objects[i];
final fields = _fields[i];
serializer.writeUint(
type.declaredNullability == ast.Nullability.nullable ? 1 : 0,
);
serializer.writeRefId(fields.typeParameters);
serializer.writeRefId(type.returnType);
serializer.writeRefId(fields.parameterTypes);
serializer.writeRefId(fields.parameterNames);
serializer.writeUint(
1 /* implicit closure parameter */ + type.requiredParameterCount,
);
}
}
}
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 = [];
// Filled by FunctionTypeSerializationCluster when tracing corresponding
// function types, as there is no link in the AST.
final Map<ast.StructuralParameter, ast.FunctionType>
_structuralParameterOwner = {};
Object _getTypeParameterOwner(
SnapshotSerializer serializer,
ast.GenericDeclaration declaration,
) {
switch (declaration) {
case ast.Class():
return declaration;
case ast.Procedure():
return serializer.functionRegistry.getFunction(
declaration,
isGetter: declaration.isGetter,
isSetter: declaration.isSetter,
);
case ast.LocalFunction():
return serializer.functionRegistry.getFunction(
getEnclosingMember(declaration),
localFunction: declaration,
);
default:
throw 'Unexpected type parameter declaration ${declaration.runtimeType} $declaration';
}
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final type = object as ast.DartType;
_objects.add(type);
switch (type) {
case ast.TypeParameterType():
serializer.push(
_getTypeParameterOwner(serializer, type.parameter.declaration!),
);
break;
case ast.StructuralParameterType():
serializer.push(_structuralParameterOwner[type.parameter]!);
break;
default:
throw 'Unexpected type parameter ${type.runtimeType} $type';
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.typeParameterTypes.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; i < _objects.length; i++) {
final type = _objects[i];
serializer.writeUint(
type.declaredNullability == ast.Nullability.nullable ? 1 : 0,
);
Object owner;
int index;
switch (type) {
case ast.TypeParameterType():
owner = _getTypeParameterOwner(
serializer,
type.parameter.declaration!,
);
index = computeIndexOfTypeParameter(type.parameter);
break;
case ast.StructuralParameterType():
final functionType = _structuralParameterOwner[type.parameter]!;
owner = functionType;
index = functionType.typeParameters.indexOf(type.parameter);
break;
default:
throw 'Unexpected type parameter ${type.runtimeType} $type';
}
assert(index >= 0);
serializer.writeRefId(owner);
serializer.writeUint(index);
}
}
}
final class CodeSerializationCluster extends SerializationCluster {
final List<Code> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final code = object as Code;
assert(
(_objects.isEmpty && code.instructionsImageOffset == 0) ||
_objects.last.instructionsImageOffset! +
_objects.last.instructions.lengthInBytes ==
code.instructionsImageOffset,
);
_objects.add(code);
serializer.push(code.function);
serializer.push(code.objectPool);
serializer.push(code.exceptionHandlers);
serializer.push(code.pcDescriptors);
serializer.push(code.catchEntryMoves);
serializer.push(code.codeSourceMap);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.codes.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final code in _objects) {
serializer.writeRefId(code.objectPool);
serializer.writeRefId(code.function);
serializer.writeRefId(code.exceptionHandlers);
serializer.writeRefId(code.pcDescriptors);
serializer.writeRefId(code.catchEntryMoves);
serializer.writeRefId(code.codeSourceMap);
serializer.writeUint(code.instructions.lengthInBytes);
}
}
}
class ICData(
final CFunction owner,
final ArgumentsShape argumentsShape,
final Name targetName,
);
final class ICDataSerializationCluster extends SerializationCluster {
final List<ICData> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final obj = object as ICData;
_objects.add(obj);
serializer.push(obj.targetName);
serializer.push(obj.argumentsShape);
serializer.push(obj.owner);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.icDatas.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final obj in _objects) {
serializer.writeRefId(obj.targetName);
serializer.writeRefId(obj.argumentsShape);
serializer.writeRefId(obj.owner);
}
}
}
final class SubtypeTestCacheSerializationCluster extends SerializationCluster {
final List<SubtypeTestCache> _objects = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final obj = object as SubtypeTestCache;
_objects.add(obj);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.subtypeTestCaches.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final obj in _objects) {
serializer.assignRef(obj);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final obj in _objects) {
serializer.writeUint(obj.numInputs);
}
}
}
final class ObjectPoolSerializationCluster extends SerializationCluster {
final List<ObjectPool> _objects = [];
final Map<ICDataCallEntry, ICData> icDatas = {};
@override
void trace(SnapshotSerializer serializer, Object object) {
final pool = object as ObjectPool;
_objects.add(pool);
for (final entry in pool.entries) {
if (entry is SpecializedEntry) {
switch (entry) {
case NewObjectTags():
serializer.push(entry.cls);
case StaticFieldOffset():
serializer.push(entry.field);
case ICDataCallEntry():
final icData = icDatas[entry] = ICData(
entry.owner,
entry.argumentsShape,
entry.selector,
);
serializer.push(icData);
case SubtypeTestCacheWithName():
serializer.push(entry.stc);
serializer.push(entry.name);
case ReservedEntry():
break;
}
} else if (entry is! UnboxedConstant) {
serializer.push(entry);
}
}
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.objectPools.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final pool in _objects) {
serializer.assignRef(pool);
serializer.writeUint(pool.entries.length);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final pool in _objects) {
serializer.writeUint(pool.entries.length);
for (final entry in pool.entries) {
if (entry is SpecializedEntry) {
switch (entry) {
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]);
case DynamicCallEntry():
serializer.writeUint(ObjectPoolEntryKind.dynamicCall.index);
serializer.writeRefId(icDatas[entry]);
case SubtypeTestCacheWithName():
serializer.writeUint(ObjectPoolEntryKind.objectRef.index);
serializer.writeRefId(entry.stc);
serializer.writeUint(ObjectPoolEntryKind.objectRef.index);
serializer.writeRefId(entry.name);
case ReservedEntry():
}
} else if (entry is UnboxedIntConstant) {
serializer.writeUint(ObjectPoolEntryKind.unboxedInt.index);
serializer.out.writeInt(entry.value);
} else if (entry is UnboxedDoubleConstant) {
serializer.writeUint(ObjectPoolEntryKind.unboxedDouble.index);
serializer.out.writeDouble(entry.value);
} else {
serializer.writeUint(ObjectPoolEntryKind.objectRef.index);
serializer.writeRefId(entry);
}
}
}
}
}
final class ExceptionHandlersSerializationCluster extends SerializationCluster {
final List<ExceptionHandlers> _objects = [];
final List<ast.ListConstant> _guardTypes = [];
@override
void trace(SnapshotSerializer serializer, Object object) {
final exceptionHandlers = object as ExceptionHandlers;
_objects.add(exceptionHandlers);
final guardTypes = getListConstant([
for (final handler in exceptionHandlers.handlers)
getListConstant(handler.guardTypes),
]);
_guardTypes.add(guardTypes);
serializer.push(guardTypes);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.exceptionHandlers.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final eh in _objects) {
serializer.assignRef(eh);
serializer.writeUint(eh.handlers.length);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (var i = 0, n = _objects.length; i < n; ++i) {
final eh = _objects[i];
final guardTypes = _guardTypes[i];
serializer.writeUint(eh.handlers.length);
serializer.writeUint(eh.hasAsyncHandler ? 1 : 0);
serializer.writeRefId(guardTypes);
for (final handler in eh.handlers) {
serializer.writeUint(handler.pcOffset);
serializer.writeUint(handler.outerIndex + 1);
serializer.writeUint(handler.needsStackTrace ? 1 : 0);
serializer.writeUint(handler.hasCatchAll ? 1 : 0);
serializer.writeUint(handler.isSynthetic ? 1 : 0);
}
}
}
}
final class PcDescriptorsSerializationCluster extends SerializationCluster {
final List<PcDescriptors> _objects = [];
final List<SnapshotStreamWriter> _encoded = [];
SnapshotStreamWriter _encode(
SnapshotSerializer serializer,
PcDescriptors pcDescriptors,
) {
final vmOffsets = serializer.objectLayout.vmOffsets;
final stream = SnapshotStreamWriter(initialSize: 32);
var previousPcOffset = 0;
var previousFileOffset = 0;
for (final cs in pcDescriptors.callSites) {
final packedFields =
(PcDescriptorKind.Other.index <<
vmOffsets.UntaggedPcDescriptors_kKindBitsPos) |
((cs.exceptionHandlerIndex + 1) <<
vmOffsets.UntaggedPcDescriptors_kTryIndexBitsPos);
stream.writeSLEB128(packedFields);
final pcOffset = cs.pcOffset;
stream.writeSLEB128(pcOffset - previousPcOffset);
previousPcOffset = pcOffset;
stream.writeSLEB128(0); // Delta-encoded deoptId.
final fileOffset = cs.sourcePosition.fileOffset;
stream.writeSLEB128(fileOffset - previousFileOffset);
previousFileOffset = fileOffset;
}
return stream;
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final pcDescriptors = object as PcDescriptors;
_objects.add(pcDescriptors);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.pcDescriptors.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final pcDescriptors in _objects) {
serializer.assignRef(pcDescriptors);
final encoded = _encode(serializer, pcDescriptors);
_encoded.add(encoded);
serializer.writeUint(encoded.position);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final encoded in _encoded) {
serializer.writeUint(encoded.position);
for (final buf in encoded.getContents()) {
serializer.out.writeUint8List(buf);
}
}
}
}
final class CatchEntryMovesSerializationCluster extends SerializationCluster {
final List<CatchEntryMoves> _objects = [];
final List<SnapshotStreamWriter> _encoded = [];
SnapshotStreamWriter _encode(
SnapshotSerializer serializer,
CatchEntryMoves catchEntryMoves,
) {
final stream = SnapshotStreamWriter(initialSize: 16);
for (final es in catchEntryMoves.exceptionSites) {
stream.writeInt(es.pcOffset);
// TODO: write moves
stream.writeInt(0); // length
stream.writeInt(0); // suffix length
stream.writeInt(0); // suffix offset
}
return stream;
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final catchEntryMoves = object as CatchEntryMoves;
_objects.add(catchEntryMoves);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.catchEntryMoves.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final catchEntryMoves in _objects) {
serializer.assignRef(catchEntryMoves);
final encoded = _encode(serializer, catchEntryMoves);
_encoded.add(encoded);
serializer.writeUint(encoded.position);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final encoded in _encoded) {
serializer.writeUint(encoded.position);
for (final buf in encoded.getContents()) {
serializer.out.writeUint8List(buf);
}
}
}
}
enum CodeSourceMapOp {
changePosition,
advancePC,
pushFunction,
popFunction,
nullCheck,
}
final class CodeSourceMapSerializationCluster extends SerializationCluster {
final List<CodeSourceMap> _objects = [];
final List<SnapshotStreamWriter> _encoded = [];
static const int opBits = 3;
static const int argMask = (1 << (32 - opBits)) - 1;
static const int startPosition = -12; // kDartCodePrologue
SnapshotStreamWriter _encode(
SnapshotSerializer serializer,
CodeSourceMap codeSourceMap,
) {
final stream = SnapshotStreamWriter(initialSize: 16);
var previousPcOffset = 0;
var previousFileOffset = startPosition;
for (final sp in codeSourceMap.sourcePositions) {
// TODO: encode inline stacks
final fileOffset = sp.sourcePosition.fileOffset;
stream.writeInt(
CodeSourceMapOp.changePosition.index |
((fileOffset - previousFileOffset) & argMask) << opBits,
);
previousFileOffset = fileOffset;
final pcOffset = sp.pcOffset;
stream.writeInt(
CodeSourceMapOp.advancePC.index |
((pcOffset - previousPcOffset) & argMask) << opBits,
);
previousPcOffset = pcOffset;
}
return stream;
}
@override
void trace(SnapshotSerializer serializer, Object object) {
final codeSourceMap = object as CodeSourceMap;
_objects.add(codeSourceMap);
}
@override
void writePreLoad(SnapshotSerializer serializer) {
serializer.writeUint(PredefinedClusters.codeSourceMap.index);
}
@override
void writeAlloc(SnapshotSerializer serializer) {
serializer.writeUint(_objects.length);
for (final codeSourceMap in _objects) {
serializer.assignRef(codeSourceMap);
final encoded = _encode(serializer, codeSourceMap);
_encoded.add(encoded);
serializer.writeUint(encoded.position);
}
}
@override
void writeFill(SnapshotSerializer serializer) {
for (final encoded in _encoded) {
serializer.writeUint(encoded.position);
for (final buf in encoded.getContents()) {
serializer.out.writeUint8List(buf);
}
}
}
}
/// Buffers snapshot writing.
class SnapshotStreamWriter {
// Constants for variable-length encoding used by snapshots.
static const int dataBitsPerByte = 7;
static const int byteMask = (1 << dataBitsPerByte) - 1;
static const int maxUnsignedDataPerByte = byteMask;
static const int minDataPerByte = -(1 << (dataBitsPerByte - 1));
static const int maxDataPerByte = (~minDataPerByte) & byteMask;
static const int endByteMarker = 255 - maxDataPerByte;
static const int endUnsignedByteMarker = 255 - maxUnsignedDataPerByte;
// Prefix of the data stored in this writer.
List<(Uint8List, int length)> _buffers = [];
// Total length of data in [_buffers].
int _buffersLength = 0;
Uint8List _currentBuffer;
int _currentLength = 0;
SnapshotStreamWriter({int initialSize = 1024})
: _currentBuffer = Uint8List(initialSize);
List<Uint8List> getContents() {
final list = <Uint8List>[];
for (var (buf, len) in _buffers) {
if (len != buf.length) {
buf = Uint8List.view(buf.buffer, buf.offsetInBytes, len);
}
list.add(buf);
}
Uint8List buf = _currentBuffer;
if (_currentLength != buf.length) {
buf = Uint8List.view(buf.buffer, buf.offsetInBytes, _currentLength);
}
list.add(buf);
return list;
}
int get position => _buffersLength + _currentLength;
@pragma('vm:never-inline')
void _grow(int minLength) {
int nextBufferSize = _currentBuffer.length << 1;
while (nextBufferSize < minLength) {
nextBufferSize = nextBufferSize << 1;
}
_buffers.add((_currentBuffer, _currentLength));
_buffersLength += _currentLength;
_currentBuffer = Uint8List(nextBufferSize);
_currentLength = 0;
}
@pragma('vm:prefer-inline')
void _ensureCapacity(int length) {
assert(length >= 0);
if (length > _currentBuffer.length - _currentLength) {
_grow(length);
}
}
@pragma('vm:prefer-inline')
void reserve(int length) {
if (length != 0) {
_ensureCapacity(length);
_currentLength += length;
}
}
@pragma('vm:prefer-inline')
void align(int alignment) {
final pos = this.position;
int padding = roundUp(pos, alignment) - pos;
reserve(padding);
}
@pragma('vm:prefer-inline')
void writeByte(int value) {
_ensureCapacity(1);
_currentBuffer[_currentLength++] = value;
}
@pragma('vm:prefer-inline')
void writeUint8List(List<int> src) {
_ensureCapacity(src.length);
_currentBuffer.setRange(_currentLength, _currentLength + src.length, src);
_currentLength += src.length;
}
@pragma('vm:prefer-inline')
void writeUtf16String(String string) {
_ensureCapacity(string.length << 1);
for (var i = 0; i < string.length; ++i) {
int utf16codeUnit = string.codeUnitAt(i);
_currentBuffer[_currentLength++] = utf16codeUnit & 0xff;
_currentBuffer[_currentLength++] = utf16codeUnit >> 8;
}
}
@pragma('vm:prefer-inline')
void writeInt(int value) {
while (value < minDataPerByte || value > maxDataPerByte) {
writeByte(value & byteMask);
value = value >> dataBitsPerByte;
}
writeByte(value + endByteMarker);
}
@pragma('vm:prefer-inline')
void writeUint(int value) {
assert(value >= 0);
while (value > maxUnsignedDataPerByte) {
writeByte(value & byteMask);
value = value >>> dataBitsPerByte;
}
writeByte(value + endUnsignedByteMarker);
}
void writeRefId(int value) {
assert((value >>> 28) == 0);
_ensureCapacity(4);
if ((value >> 21) != 0) {
_currentBuffer[_currentLength++] = (value >> 21) & 127;
}
if ((value >> 14) != 0) {
_currentBuffer[_currentLength++] = (value >> 14) & 127;
}
if ((value >> 7) != 0) {
_currentBuffer[_currentLength++] = (value >> 7) & 127;
}
_currentBuffer[_currentLength++] = ((value >> 0) & 127) | 128;
}
@pragma('vm:prefer-inline')
void writeSLEB128(int value) {
var last = false;
do {
int part = value & 0x7f;
value >>= 7;
if ((value == 0 && (part & 0x40) == 0) ||
(value == -1 && (part & 0x40) != 0)) {
last = true;
} else {
part |= 0x80;
}
writeByte(part);
} while (!last);
}
void writeDouble(double value) {
writeInt(doubleToIntBits(value));
}
ByteData _bufferAt(int offset) {
if (offset >= _buffersLength) {
return _currentBuffer.buffer.asByteData(offset - _buffersLength);
} else {
for (var (buf, len) in _buffers) {
if (offset < len) {
return buf.buffer.asByteData(offset);
}
offset -= len;
}
throw 'Mismatch between _buffers and _buffersLength';
}
}
/// Write 32-bit value at given offset.
/// The space for the value should be reserved by a single [reserve].
void writeInt32At(int offset, int value) {
_bufferAt(offset).setInt32(0, value, Snapshot.endianness);
}
/// Write 64-bit value at given offset.
/// The space for the value should be reserved by a single [reserve].
void writeInt64At(int offset, int value) {
_bufferAt(offset).setInt64(0, value, Snapshot.endianness);
}
}