c56a8be4e5
Change-Id: I4e51e10928ccc26c18f2a63f6cb6b23cd618d853 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426003 Commit-Queue: Mayank Patke <fishythefish@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Nate Biggs <natebiggs@google.com>
517 lines
17 KiB
Dart
517 lines
17 KiB
Dart
// 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());
|
|
}
|