blob: 5703f70a55e4eddbccb80f9d0737286bd2bca118 [file] [log] [blame] [edit]
// Copyright (c) 2025, 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:io';
import 'dart:typed_data';
import 'package:kernel/ast.dart';
import 'package:kernel/binary/ast_from_binary.dart'
show BinaryBuilderWithMetadata;
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart'
show writeComponentToBinary, writeComponentToBytes;
import 'package:kernel/library_index.dart';
import 'package:path/path.dart' as path;
import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository;
import 'package:vm/metadata/inferred_type.dart'
show
InferredArgTypeMetadataRepository,
InferredReturnTypeMetadataRepository,
InferredTypeMetadataRepository;
import 'package:vm/metadata/procedure_attributes.dart'
show ProcedureAttributesMetadataRepository;
import 'package:vm/metadata/table_selector.dart';
import 'class_info.dart';
import 'compiler_options.dart';
import 'dispatch_table.dart';
import 'dynamic_modules.dart';
import 'js/method_collector.dart' show JSMethods;
import 'serialization.dart';
import 'translator.dart';
const String dynamicMainModuleProcedureAttributeMetadataTag =
'dynMod:procedureAttributes';
const String dynamicMainModuleSelectorMetadataTag = 'dynMod:selectors';
/// Repository for kernel global entity IDs.
///
/// Each class and member gets annotated with a unique ID that will allow us to
/// persist metadata about that entity across compilations.
class DynamicModuleGlobalIdRepository extends MetadataRepository<int> {
static const repositoryTag = 'wasm.dynamic-modules.globalId';
@override
final String tag = repositoryTag;
@override
final Map<TreeNode, int> mapping = {};
@override
int readFromBinary(Node node, BinarySource source) {
return source.readUInt30();
}
@override
void writeToBinary(int globalId, Node node, BinarySink sink) {
sink.writeUInt30(globalId);
}
}
/// Repository for kernel constants.
class DynamicModuleConstantRepository
extends MetadataRepository<Map<Constant, int>> {
static const repositoryTag = 'wasm.dynamic-modules.constants';
@override
final String tag = repositoryTag;
@override
final Map<TreeNode, Map<Constant, int>> mapping = {};
@override
Map<Constant, int> readFromBinary(Node node, BinarySource source) {
final length = source.readUInt30();
final Map<Constant, int> constants = {};
for (int i = 0; i < length; i++) {
final constant = source.readConstantReference();
final id = source.readUInt30();
constants[constant] = id;
}
return constants;
}
@override
void writeToBinary(Map<Constant, int> constants, Node node, BinarySink sink) {
sink.writeUInt30(constants.length);
constants.forEach((constant, id) {
sink.writeConstantReference(constant);
sink.writeUInt30(id);
});
}
}
class ClassMetadata {
/// The class numbering ID assigned to this class.
final int classId;
/// The brand index attached to the wasm struct representing this class, if
/// any.
final int? brandIndex;
ClassMetadata._(this.classId, this.brandIndex);
factory ClassMetadata.deserialize(DataDeserializer source) {
final classId = source.readInt() - 1;
final brandIndex = source.readInt();
return ClassMetadata._(classId, brandIndex == 0 ? null : brandIndex - 1);
}
void serialize(DataSerializer sink) {
sink.writeInt(classId + 1);
sink.writeInt(brandIndex == null ? 0 : brandIndex! + 1);
}
}
class SelectorMetadata {
final int id;
final String name;
final int callCount;
final bool isSetter;
final bool useMultipleEntryPoints;
final bool isDynamicSubmoduleOverridable;
final bool isDynamicSubmoduleCallable;
final bool isNoSuchMethod;
final SelectorTargets? checked;
final SelectorTargets? unchecked;
final SelectorTargets? normal;
final List<Reference> references;
SelectorMetadata(
this.id,
this.name,
this.callCount,
this.isSetter,
this.useMultipleEntryPoints,
this.isDynamicSubmoduleOverridable,
this.isDynamicSubmoduleCallable,
this.isNoSuchMethod,
this.checked,
this.unchecked,
this.normal,
this.references);
void serialize(DataSerializer sink) {
sink.writeInt(id);
sink.writeString(name);
sink.writeInt(callCount);
sink.writeBoolList([
isSetter,
useMultipleEntryPoints,
isDynamicSubmoduleOverridable,
isDynamicSubmoduleCallable,
isNoSuchMethod
]);
sink.writeNullable(checked, (targets) => targets.serialize(sink));
sink.writeNullable(unchecked, (targets) => targets.serialize(sink));
sink.writeNullable(normal, (targets) => targets.serialize(sink));
sink.writeList(references, sink.writeReference);
}
factory SelectorMetadata.deserialize(DataDeserializer source) {
final id = source.readInt();
final name = source.readString();
final callCount = source.readInt();
final [
isSetter,
useMultipleEntryPoints,
isDynamicSubmoduleOverridable,
isDynamicSubmoduleCallable,
isNoSuchMethod
] = source.readBoolList();
final checked =
source.readNullable(() => SelectorTargets.deserialize(source));
final unchecked =
source.readNullable(() => SelectorTargets.deserialize(source));
final normal =
source.readNullable(() => SelectorTargets.deserialize(source));
final references = source.readList(source.readReference);
return SelectorMetadata(
id,
name,
callCount,
isSetter,
useMultipleEntryPoints,
isDynamicSubmoduleOverridable,
isDynamicSubmoduleCallable,
isNoSuchMethod,
checked,
unchecked,
normal,
references);
}
}
class DispatchTableMetadata {
final List<SelectorMetadata> selectors;
final List<Reference?> table;
// Ignore dynamic selectors since dynamic calls are not allowed from
// submodules.
DispatchTableMetadata(this.selectors, this.table);
}
/// Metadata produced by the main module.
///
/// This data will get serialized as part of the main module compilation process
/// and will be provided as an input to be deserialized by subsequent submodule
/// compilations.
class MainModuleMetadata {
/// Class to metadata about the class.
final Map<Class, ClassMetadata> classMetadata;
/// Maps dynamic callable references to a unique ID that is used to generate
/// the export name for the reference.
final Map<Reference, int> callableReferenceIds;
late final DispatchTable dispatchTable;
/// Contains each invoked reference that targets an updateable function.
final Set<Reference> invokedOverridableReferences;
/// Maps invocation keys (either selector or builtin) to the implementation's
/// index in the runtime table. Key includes whether the key was invoked
/// with unchecked entry.
final Map<int, int> keyInvocationToIndex;
/// Classes in dfs order.
final List<Class> dfsOrderClassIds;
/// Saved flags from the main module to verify that settings have not changed
/// between main module invocation and submodule invocation.
final TranslatorOptions mainModuleTranslatorOptions;
final Map<String, String> mainModuleEnvironment;
MainModuleMetadata._(
this.classMetadata,
this.callableReferenceIds,
this.dispatchTable,
this.invokedOverridableReferences,
this.keyInvocationToIndex,
this.dfsOrderClassIds,
this.mainModuleTranslatorOptions,
this.mainModuleEnvironment);
MainModuleMetadata.empty(
this.mainModuleTranslatorOptions, this.mainModuleEnvironment)
: classMetadata = {},
callableReferenceIds = {},
invokedOverridableReferences = {},
keyInvocationToIndex = {},
dfsOrderClassIds = [];
void finalize(Translator translator) {
translator.classInfo.forEach((cls, info) {
final id =
cls.isAnonymousMixin ? -1 : (info.classId as AbsoluteClassId).value;
final structType = info.struct;
final brandIndex =
translator.typesBuilder.brandTypeAssignments[structType];
classMetadata[cls] = ClassMetadata._(id, brandIndex);
});
dispatchTable = translator.dispatchTable;
dfsOrderClassIds.addAll(translator.classIdNumbering.dfsOrder);
// Annotate classes and procedures with indices for serialization.
int nextId = 0;
final idRepo = translator
.component.metadata[DynamicModuleGlobalIdRepository.repositoryTag]!;
void annotateMember(Member member) {
idRepo.mapping[member] = nextId++;
}
for (final lib in translator.component.libraries) {
for (final member in lib.members) {
annotateMember(member);
}
for (final cls in lib.classes) {
idRepo.mapping[cls] = nextId++;
for (final member in cls.members) {
annotateMember(member);
}
}
}
}
void serialize(DataSerializer sink, Translator translator) {
finalize(translator);
sink.writeMap(classMetadata, sink.writeClass, (m) => m.serialize(sink));
sink.writeMap(callableReferenceIds, sink.writeReference, sink.writeInt);
dispatchTable.serialize(sink);
sink.writeList(invokedOverridableReferences, sink.writeReference);
sink.writeMap(keyInvocationToIndex, sink.writeInt, sink.writeInt);
sink.writeList(dfsOrderClassIds, sink.writeClass);
mainModuleTranslatorOptions.serialize(sink);
sink.writeMap(mainModuleEnvironment, sink.writeString, sink.writeString);
}
static MainModuleMetadata deserialize(DataDeserializer source) {
final classMetadata = source.readMap(
source.readClass, () => ClassMetadata.deserialize(source));
final callableReferenceIds =
source.readMap(source.readReference, source.readInt);
final dispatchTable = DispatchTable.deserialize(source);
final invokedReferences = source.readList(source.readReference).toSet();
final keyInvocationToIndex = source.readMap(source.readInt, source.readInt);
final dfsOrderClasses = source.readList(source.readClass);
final mainModuleTranslatorOptions = TranslatorOptions.deserialize(source);
final mainModuleEnvironment =
source.readMap(source.readString, source.readString);
final metadata = MainModuleMetadata._(
classMetadata,
callableReferenceIds,
dispatchTable,
invokedReferences,
keyInvocationToIndex,
dfsOrderClasses,
mainModuleTranslatorOptions,
mainModuleEnvironment);
return metadata;
}
static void verifyMainModuleOptions(WasmCompilerOptions options) {
final translatorOptions = options.translatorOptions;
if (translatorOptions.enableDeferredLoading) {
throw StateError(
'Cannot use enable-deferred-loading with dynamic modules.');
}
if (translatorOptions.enableMultiModuleStressTestMode) {
throw StateError(
'Cannot use multi-module-stress-test-mode with dynamic modules.');
}
if (translatorOptions.enableMultiModuleStressTestMode) {
throw StateError(
'Cannot use multi-module-stress-test-mode with dynamic modules.');
}
}
void verifyDynamicSubmoduleOptions(WasmCompilerOptions options) {
final translatorOptions = options.translatorOptions;
Never fail(String optionName) {
throw StateError(
'Inconsistent flag for dynamic submodule compilation: $optionName');
}
// TODO(natebiggs): Disallow certain flags from being used in conjunction
// with submodules.
if (translatorOptions.enableAsserts !=
mainModuleTranslatorOptions.enableAsserts) {
fail('enable-asserts');
}
if (translatorOptions.importSharedMemory !=
mainModuleTranslatorOptions.importSharedMemory) {
fail('import-shared-memory');
}
if (translatorOptions.inlining != translatorOptions.inlining) {
fail('inlining');
}
if (translatorOptions.jsCompatibility !=
mainModuleTranslatorOptions.jsCompatibility) {
fail('js-compatibility');
}
if (translatorOptions.omitImplicitTypeChecks !=
mainModuleTranslatorOptions.omitImplicitTypeChecks) {
fail('omit-implicit-checks');
}
if (translatorOptions.omitExplicitTypeChecks !=
mainModuleTranslatorOptions.omitExplicitTypeChecks) {
fail('omit-explicit-checks');
}
if (translatorOptions.omitBoundsChecks !=
mainModuleTranslatorOptions.omitBoundsChecks) {
fail('omit-bounds-checks');
}
if (translatorOptions.polymorphicSpecialization !=
mainModuleTranslatorOptions.polymorphicSpecialization) {
fail('polymorphic-specialization');
}
// Skip printKernel
// Skip printWasm
if (translatorOptions.minify != mainModuleTranslatorOptions.minify) {
fail('minify');
}
if (translatorOptions.verifyTypeChecks !=
mainModuleTranslatorOptions.verifyTypeChecks) {
fail('verify-type-checks');
}
// Skip verbose
if (translatorOptions.enableExperimentalFfi !=
mainModuleTranslatorOptions.enableExperimentalFfi) {
fail('enable-experimental-ffi');
}
if (translatorOptions.enableExperimentalWasmInterop !=
mainModuleTranslatorOptions.enableExperimentalWasmInterop) {
fail('enable-experimental-wasm-interop');
}
// Skip generate source maps
if (translatorOptions.enableDeferredLoading !=
mainModuleTranslatorOptions.enableDeferredLoading) {
fail('enable-deferred-loading');
}
if (translatorOptions.enableMultiModuleStressTestMode !=
mainModuleTranslatorOptions.enableMultiModuleStressTestMode) {
fail('enable-multi-module-stress-test-mode');
}
if (translatorOptions.inliningLimit !=
mainModuleTranslatorOptions.inliningLimit) {
fail('inlining-limit');
}
if (translatorOptions.sharedMemoryMaxPages !=
mainModuleTranslatorOptions.sharedMemoryMaxPages) {
fail('shared-memory-max-pages');
}
if (!mapEquals(options.environment, mainModuleEnvironment)) {
fail('environment mismatch');
}
}
}
String _makeOptDillPath(String path) =>
'${path.substring(0, path.length - '.dill'.length)}.opt.dill';
Future<void> serializeMainModuleComponent(
Component component, Uri dynamicModuleMainUri,
{required bool optimized}) async {
// TODO(natebiggs): Serialize as a summary and filter to only necessary
// libraries.
await writeComponentToBinary(
component,
optimized
? _makeOptDillPath(dynamicModuleMainUri.path)
: dynamicModuleMainUri.path,
includeSource: false);
}
Future<(Component, JSMethods)> generateDynamicSubmoduleComponent(
Component component,
CoreTypes coreTypes,
Uri dynamicModuleMainUri,
JSMethods jsInteropMethods) async {
final submoduleComponentBytes = writeComponentToBytes(
Component(libraries: component.getDynamicSubmoduleLibraries(coreTypes)));
final optimizedMainComponentBytes =
await File(_makeOptDillPath(dynamicModuleMainUri.path)).readAsBytes();
final concatenatedComponentBytes = Uint8List(
submoduleComponentBytes.length + optimizedMainComponentBytes.length);
concatenatedComponentBytes.setAll(0, optimizedMainComponentBytes);
concatenatedComponentBytes.setAll(
optimizedMainComponentBytes.length, submoduleComponentBytes);
final newComponent = Component()
..addMetadataRepository(DynamicModuleGlobalIdRepository())
..addMetadataRepository(DynamicModuleConstantRepository())
..addMetadataRepository(ProcedureAttributesMetadataRepository())
..addMetadataRepository(TableSelectorMetadataRepository())
..addMetadataRepository(DirectCallMetadataRepository())
..addMetadataRepository(InferredTypeMetadataRepository())
..addMetadataRepository(InferredReturnTypeMetadataRepository())
..addMetadataRepository(InferredArgTypeMetadataRepository());
BinaryBuilderWithMetadata(concatenatedComponentBytes)
.readComponent(newComponent);
// Remap js interop methods into the new component.
final index = LibraryIndex.all(component);
final JSMethods newJsMethods = {};
jsInteropMethods.forEach((method, info) {
newJsMethods[index.getProcedure(
method.enclosingLibrary.importUri.path,
method.enclosingClass?.name ?? LibraryIndex.topLevel,
method.name.text)] = info;
});
return (newComponent, newJsMethods);
}
Future<MainModuleMetadata> deserializeMainModuleMetadata(
Component component, WasmCompilerOptions options) async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
final dynamicModuleMetadataBytes = await File.fromUri(filename).readAsBytes();
final source = DataDeserializer(dynamicModuleMetadataBytes, component);
return MainModuleMetadata.deserialize(source);
}
Future<void> serializeMainModuleMetadata(Component component,
Translator translator, WasmCompilerOptions options) async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
final serializer = DataSerializer(component);
translator.dynamicModuleInfo!.metadata.serialize(serializer, translator);
await File.fromUri(filename).writeAsBytes(serializer.takeBytes());
}