[dart2wasm] Dynamic modules
Missing from this implementation: - Closure/dynamic calls with differing signatures - Overrides with extra optional parameters - Records with same shape defined in different dynamic modules - Avoiding running TFA on dynamic module. - Recompilation of only updateable functions from main module. - Persist wasm def types from main module. Testing is currently done locally via the dynamic_modules package test suite: dart pkg/dynamic_modules/test/runner/main.dart --runtime=dart2wasm Immediately after this lands we can introduce a new step to one of the wasm test matrix configurations that runs the above test suite (the VM has a similar configuration). Change-Id: I3386d84be11b773842d45f4268a62a54c47e352b Tested: Tested via new tests in dynamic_modules package. Tests run locally but will add to existing config. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397721 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
@@ -416,6 +416,9 @@ const main = async () => {
|
||||
loadDeferredWasm: async (moduleName) => {
|
||||
let filename = wasmFilename.replace('.wasm', `_${moduleName}.wasm`);
|
||||
return readBytes(filename);
|
||||
},
|
||||
loadDynamicModule: async (uri) => {
|
||||
return readBytes(uri);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'dart:math';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
import 'dynamic_modules.dart';
|
||||
import 'translator.dart';
|
||||
|
||||
/// Wasm struct field indices for fields that are accessed explicitly from Wasm
|
||||
@@ -43,6 +44,7 @@ class FieldIndex {
|
||||
static const instantiationContextInner = 0;
|
||||
static const instantiationContextTypeArgumentsBase = 1;
|
||||
static const typeIsDeclaredNullable = 2;
|
||||
static const interfaceTypeClassId = 3;
|
||||
static const interfaceTypeTypeArguments = 4;
|
||||
static const functionTypeNamedParameters = 9;
|
||||
static const recordTypeNames = 3;
|
||||
@@ -154,14 +156,14 @@ class ClassInfo {
|
||||
final Class? cls;
|
||||
|
||||
/// The Class ID of this class, stored in every instance of the class.
|
||||
int get classId {
|
||||
if (_classId == anonymousMixinClassId) {
|
||||
ClassId get classId {
|
||||
if (_classId._localValue == anonymousMixinClassId) {
|
||||
throw 'Tried to access class ID of anonymous mixin $cls';
|
||||
}
|
||||
return _classId;
|
||||
}
|
||||
|
||||
final int _classId;
|
||||
final ClassId _classId;
|
||||
|
||||
/// Depth of this class in the Wasm type hierarchy.
|
||||
final int depth;
|
||||
@@ -216,6 +218,12 @@ class ClassInfo {
|
||||
for (var fieldType in struct.fields.skip(FieldIndex.objectFieldBase))
|
||||
fieldType.type.unpacked
|
||||
];
|
||||
|
||||
void forEachClassFieldIndex(void Function(int index, w.FieldType type) f) {
|
||||
for (int i = FieldIndex.objectFieldBase; i < struct.fields.length; i++) {
|
||||
f(i, struct.fields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassInfo upperBound(ClassInfo a, ClassInfo b) {
|
||||
@@ -301,11 +309,11 @@ class ClassInfoCollector {
|
||||
|
||||
void _createStructForClassTop(int classCount) {
|
||||
final w.StructType struct = translator.typesBuilder.defineStruct("#Top");
|
||||
topInfo = ClassInfo(null, 0, 0, struct, null);
|
||||
topInfo = ClassInfo(null, AbsoluteClassId(0), 0, struct, null);
|
||||
translator.classForHeapType[struct] = topInfo;
|
||||
}
|
||||
|
||||
void _createStructForClass(Map<Class, int> classIds, Class cls) {
|
||||
void _createStructForClass(Map<Class, ClassId> classIds, Class cls) {
|
||||
ClassInfo? info = translator.classInfo[cls];
|
||||
if (info != null) return;
|
||||
|
||||
@@ -364,16 +372,22 @@ class ClassInfoCollector {
|
||||
.defineStruct(cls.name, superType: superInfo.struct);
|
||||
info = ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo,
|
||||
typeParameterMatch: typeParameterMatch);
|
||||
if (translator.dynamicModuleSupportEnabled &&
|
||||
cls.isDynamicModuleExtendable(translator.coreTypes)) {
|
||||
// If a class is extendable in a dynamic module then we have to be
|
||||
// conservative and mark it as not being final.
|
||||
struct.hasAnySubtypes = true;
|
||||
}
|
||||
}
|
||||
translator.classesSupersFirst.add(info);
|
||||
translator.classInfo[cls] = info;
|
||||
translator.classForHeapType.putIfAbsent(info.struct, () => info!);
|
||||
if (classId != anonymousMixinClassId) {
|
||||
translator.classes[classId] = info;
|
||||
if (classId._localValue != anonymousMixinClassId) {
|
||||
translator.classes[classId._localValue] = info;
|
||||
}
|
||||
}
|
||||
|
||||
void _createStructForRecordClass(Map<Class, int> classIds, Class cls) {
|
||||
void _createStructForRecordClass(Map<Class, ClassId> classIds, Class cls) {
|
||||
final numFields = cls.fields.length;
|
||||
|
||||
final struct = _recordStructs.putIfAbsent(
|
||||
@@ -390,7 +404,7 @@ class ClassInfoCollector {
|
||||
ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo);
|
||||
|
||||
translator.classesSupersFirst.add(info);
|
||||
translator.classes[classId] = info;
|
||||
translator.classes[classId._localValue] = info;
|
||||
translator.classInfo[cls] = info;
|
||||
translator.classForHeapType.putIfAbsent(info.struct, () => info);
|
||||
}
|
||||
@@ -471,8 +485,11 @@ class ClassInfoCollector {
|
||||
|
||||
// Class infos by class-id, will be populated by the calls to
|
||||
// [_createStructForClass] and [_createStructForRecordClass] below.
|
||||
translator.classes =
|
||||
List<ClassInfo>.filled(classIdNumbering.maxClassId + 1, topInfo);
|
||||
translator.classes = List<ClassInfo>.filled(
|
||||
(classIdNumbering.maxDynamicModuleClassId ??
|
||||
classIdNumbering.maxClassId) +
|
||||
1,
|
||||
topInfo);
|
||||
|
||||
// Class infos in different order: Infos of super class and super interfaces
|
||||
// before own info.
|
||||
@@ -504,15 +521,43 @@ class ClassInfoCollector {
|
||||
// represent objects of that dart type).
|
||||
for (final cls in dfsOrder) {
|
||||
ClassInfo? representation;
|
||||
for (final range in classIdNumbering.getConcreteClassIdRanges(cls)) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
final current = translator.classes[classId];
|
||||
if (representation == null) {
|
||||
representation = current;
|
||||
continue;
|
||||
if (translator.dynamicModuleSupportEnabled &&
|
||||
cls.isDynamicModuleExtendable(translator.coreTypes)) {
|
||||
assert(!translator.builtinTypes.containsKey(cls));
|
||||
|
||||
// If a class is extendable in a dynamic module then we have to be
|
||||
// conservative and assume it might be a subclass of Object. The Object
|
||||
// class maps to topInfo because boxed values are a subtype of Object in
|
||||
// Dart but not of the object struct.
|
||||
representation = cls == translator.coreTypes.objectClass
|
||||
? translator.topInfo
|
||||
: translator.objectInfo;
|
||||
} else {
|
||||
void addRanges(List<Range> ranges) {
|
||||
for (final range in ranges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
final current = translator.classes[classId];
|
||||
if (representation == null) {
|
||||
representation = current;
|
||||
continue;
|
||||
}
|
||||
representation = upperBound(representation!, current);
|
||||
}
|
||||
}
|
||||
representation = upperBound(representation, current);
|
||||
}
|
||||
|
||||
final mainModuleConcreteRange =
|
||||
classIdNumbering.getConcreteClassIdRangeForMainModule(cls);
|
||||
final dynamicModuleConcreteRange =
|
||||
classIdNumbering.getConcreteClassIdRangeForDynamicModule(cls);
|
||||
|
||||
// Only non-extendable classes can get here so they should only have
|
||||
// concrete implementations in either the main module or the dynamic
|
||||
// module, not both.
|
||||
assert(mainModuleConcreteRange.isEmpty ||
|
||||
dynamicModuleConcreteRange.isEmpty);
|
||||
addRanges(mainModuleConcreteRange);
|
||||
addRanges(dynamicModuleConcreteRange);
|
||||
}
|
||||
final info = translator.classInfo[cls]!;
|
||||
info._repr = representation ?? info;
|
||||
@@ -537,25 +582,35 @@ class ClassInfoCollector {
|
||||
}
|
||||
|
||||
class ClassIdNumbering {
|
||||
final Translator translator;
|
||||
final Map<Class, List<Class>> _subclasses;
|
||||
final Map<Class, List<Class>> _implementors;
|
||||
final Map<Class, Range> _concreteSubclassIdRange;
|
||||
final Map<Class, List<Range>> _concreteSubclassIdRange;
|
||||
final Map<Class, List<Range>> _concreteSubclassIdRangeForDynamicModule;
|
||||
final Set<Class> _masqueraded;
|
||||
|
||||
final List<Class> dfsOrder;
|
||||
final Map<Class, int> classIds;
|
||||
final Map<Class, ClassId> classIds;
|
||||
final int maxConcreteClassId;
|
||||
final int maxClassId;
|
||||
final int? maxDynamicModuleConcreteClassId;
|
||||
final int? maxDynamicModuleClassId;
|
||||
|
||||
int get firstDynamicModuleClassId => maxClassId + 1;
|
||||
|
||||
ClassIdNumbering._(
|
||||
this.translator,
|
||||
this._subclasses,
|
||||
this._implementors,
|
||||
this._concreteSubclassIdRange,
|
||||
this._concreteSubclassIdRangeForDynamicModule,
|
||||
this._masqueraded,
|
||||
this.dfsOrder,
|
||||
this.classIds,
|
||||
this.maxConcreteClassId,
|
||||
this.maxClassId);
|
||||
this.maxClassId,
|
||||
this.maxDynamicModuleConcreteClassId,
|
||||
this.maxDynamicModuleClassId);
|
||||
|
||||
final Map<Class, Set<Class>> _transitiveImplementors = {};
|
||||
Set<Class> _getTransitiveImplementors(Class klass) {
|
||||
@@ -581,30 +636,58 @@ class ClassIdNumbering {
|
||||
return _transitiveImplementors[klass] = transitiveImplementors;
|
||||
}
|
||||
|
||||
// Maps a class to a list of class id ranges that implement/extend the given
|
||||
// class directly or transitively.
|
||||
/// Maps a class to a list of class id ranges that implement/extend the given
|
||||
/// class directly or transitively.
|
||||
///
|
||||
/// If this function is invoked from a dynamic module enabled build then it
|
||||
/// should be wrapped with [DynamicModuleInfo.callClassIdBranch] so that the
|
||||
/// checked range will be updated.
|
||||
final Map<Class, List<Range>> _concreteClassIdRanges = {};
|
||||
List<Range> getConcreteClassIdRanges(Class klass) {
|
||||
var ranges = _concreteClassIdRanges[klass];
|
||||
List<Range> getConcreteClassIdRangeForMainModule(Class klass) {
|
||||
return _getConcreteClassIdRange(
|
||||
klass, _concreteClassIdRanges, _concreteSubclassIdRange);
|
||||
}
|
||||
|
||||
final Map<Class, List<Range>> _concreteClassIdRangesForDynamicModule = {};
|
||||
List<Range> getConcreteClassIdRangeForDynamicModule(Class klass) {
|
||||
return _getConcreteClassIdRange(
|
||||
klass,
|
||||
_concreteClassIdRangesForDynamicModule,
|
||||
_concreteSubclassIdRangeForDynamicModule);
|
||||
}
|
||||
|
||||
List<Range> getConcreteClassIdRangeForCurrentModule(Class klass) {
|
||||
return translator.isDynamicModule
|
||||
? getConcreteClassIdRangeForDynamicModule(klass)
|
||||
: getConcreteClassIdRangeForMainModule(klass);
|
||||
}
|
||||
|
||||
List<Range> _getConcreteClassIdRange(Class klass,
|
||||
Map<Class, List<Range>> cache, Map<Class, List<Range>> subclasses) {
|
||||
var ranges = cache[klass];
|
||||
if (ranges != null) return ranges;
|
||||
|
||||
ranges = [];
|
||||
final transitiveImplementors = _getTransitiveImplementors(klass);
|
||||
final range = _concreteSubclassIdRange[klass]!;
|
||||
if (!range.isEmpty) ranges.add(range);
|
||||
final subclassRanges = subclasses[klass] ?? const [];
|
||||
for (final range in subclassRanges) {
|
||||
ranges.add(range);
|
||||
}
|
||||
for (final implementor in transitiveImplementors) {
|
||||
final range = _concreteSubclassIdRange[implementor]!;
|
||||
if (!range.isEmpty) ranges.add(range);
|
||||
final implementorRanges = subclasses[implementor] ?? const [];
|
||||
for (final range in implementorRanges) {
|
||||
ranges.add(range);
|
||||
}
|
||||
}
|
||||
ranges.normalize();
|
||||
|
||||
return _concreteClassIdRanges[klass] = ranges;
|
||||
return cache[klass] = ranges;
|
||||
}
|
||||
|
||||
late final int firstNonMasqueradedInterfaceClassCid = (() {
|
||||
int lastMasqueradedClassId = 0;
|
||||
for (final cls in _masqueraded) {
|
||||
final ranges = getConcreteClassIdRanges(cls);
|
||||
final ranges = getConcreteClassIdRangeForMainModule(cls);
|
||||
if (ranges.isNotEmpty) {
|
||||
lastMasqueradedClassId = max(lastMasqueradedClassId, ranges.last.end);
|
||||
}
|
||||
@@ -616,20 +699,40 @@ class ClassIdNumbering {
|
||||
Translator translator, Set<Class> masqueraded, int firstClassId) {
|
||||
// Make graph from class to its subclasses.
|
||||
late final Class root;
|
||||
int? savedMaxConcreteClassId;
|
||||
int? savedMaxClassId;
|
||||
final subclasses = <Class, List<Class>>{};
|
||||
final implementors = <Class, List<Class>>{};
|
||||
final classIds = <Class, ClassId>{};
|
||||
|
||||
final savedMapping = translator.dynamicModuleInfo?.classIdMapping;
|
||||
if (savedMapping != null) {
|
||||
savedMapping.forEach((cls, classId) {
|
||||
classIds[cls] = AbsoluteClassId(classId);
|
||||
savedMaxClassId = max(savedMaxClassId ?? -2, classId);
|
||||
if (!cls.isAbstract && !cls.isAnonymousMixin) {
|
||||
savedMaxConcreteClassId = max(savedMaxConcreteClassId ?? -2, classId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int concreteClassCount = 0;
|
||||
int abstractClassCount = 0;
|
||||
int anonymousMixinClassCount = 0;
|
||||
int alreadyAssignedCount = 0;
|
||||
for (final library in translator.component.libraries) {
|
||||
for (final cls in library.classes) {
|
||||
if (cls.isAnonymousMixin) {
|
||||
assert(cls.isAbstract);
|
||||
anonymousMixinClassCount++;
|
||||
} else if (cls.isAbstract) {
|
||||
abstractClassCount++;
|
||||
if (!classIds.containsKey(cls)) {
|
||||
if (cls.isAnonymousMixin) {
|
||||
assert(cls.isAbstract);
|
||||
anonymousMixinClassCount++;
|
||||
} else if (cls.isAbstract) {
|
||||
abstractClassCount++;
|
||||
} else {
|
||||
concreteClassCount++;
|
||||
}
|
||||
} else {
|
||||
concreteClassCount++;
|
||||
alreadyAssignedCount++;
|
||||
}
|
||||
final superClass = cls.superclass;
|
||||
if (superClass == null) {
|
||||
@@ -700,51 +803,125 @@ class ClassIdNumbering {
|
||||
}
|
||||
|
||||
// Make a list of the depth-first pre-order traversal.
|
||||
final dfsOrder = <Class>[];
|
||||
final classIds = <Class, int>{};
|
||||
final dfsOrder =
|
||||
translator.dynamicModuleInfo?.dfsOrderClassIds ?? <Class>[];
|
||||
final inDfsOrder = {...dfsOrder};
|
||||
|
||||
// Maps any class to a dense range of concrete class ids that are subclasses
|
||||
// of that class.
|
||||
final concreteSubclassRange = <Class, Range>{};
|
||||
final concreteSubclassRanges = <Class, List<Range>>{};
|
||||
final concreteSubclassRangesForDynamicModule = <Class, List<Range>>{};
|
||||
|
||||
int nextConcreteClassId = (savedMaxClassId ?? (firstClassId - 1)) + 1;
|
||||
int nextAbstractClassId = nextConcreteClassId + concreteClassCount;
|
||||
|
||||
if (classIds.isNotEmpty) {
|
||||
// Assumes that saved IDs form a contiguous region at the top of the
|
||||
// subclass tree. So if we encounter a node without a saved ID, then we do
|
||||
// not need to explore its children for saved IDs.
|
||||
Range? addSavedRanges(Class cls) {
|
||||
final savedClassId = classIds[cls];
|
||||
if (savedClassId == null) return null;
|
||||
final children = subclasses[cls] ?? const [];
|
||||
final isConcrete = !cls.isAbstract && !cls.isAnonymousMixin;
|
||||
Range? savedRange = isConcrete
|
||||
? Range(savedClassId._localValue, savedClassId._localValue)
|
||||
: null;
|
||||
for (final child in children) {
|
||||
final childRange = addSavedRanges(child);
|
||||
if (childRange != null) {
|
||||
savedRange = savedRange == null
|
||||
? Range(childRange.start, childRange.end)
|
||||
: Range(savedRange.start, max(savedRange.end, childRange.end));
|
||||
}
|
||||
}
|
||||
if (savedRange != null) {
|
||||
(concreteSubclassRanges[cls] ??= []).add(savedRange);
|
||||
}
|
||||
return savedRange;
|
||||
}
|
||||
|
||||
addSavedRanges(root);
|
||||
}
|
||||
|
||||
final subclassesRangesToBuild = savedMaxClassId != null
|
||||
? concreteSubclassRangesForDynamicModule
|
||||
: concreteSubclassRanges;
|
||||
|
||||
int nextConcreteClassId = firstClassId;
|
||||
int nextAbstractClassId = firstClassId + concreteClassCount;
|
||||
dfs(root, (Class cls) {
|
||||
dfsOrder.add(cls);
|
||||
if (!inDfsOrder.contains(cls)) {
|
||||
dfsOrder.add(cls);
|
||||
}
|
||||
if (classIds.containsKey(cls)) return nextConcreteClassId;
|
||||
if (cls.isAnonymousMixin) {
|
||||
classIds[cls] = anonymousMixinClassId;
|
||||
classIds[cls] = AbsoluteClassId(anonymousMixinClassId);
|
||||
return nextConcreteClassId;
|
||||
}
|
||||
if (cls.isAbstract) {
|
||||
var classId = classIds[cls];
|
||||
if (classId == null) classIds[cls] = nextAbstractClassId++;
|
||||
if (classId == null) {
|
||||
classIds[cls] = AbsoluteClassId(nextAbstractClassId++);
|
||||
}
|
||||
return nextConcreteClassId;
|
||||
}
|
||||
|
||||
assert(classIds[cls] == null);
|
||||
classIds[cls] = nextConcreteClassId++;
|
||||
final classId = nextConcreteClassId++;
|
||||
classIds[cls] = savedMaxClassId != null
|
||||
? RelativeClassId(classId)
|
||||
: AbsoluteClassId(classId);
|
||||
return nextConcreteClassId - 1;
|
||||
}, (Class cls, int firstClassId) {
|
||||
final range = Range(firstClassId, nextConcreteClassId - 1);
|
||||
concreteSubclassRange[cls] = range;
|
||||
if (!range.isEmpty) {
|
||||
(subclassesRangesToBuild[cls] ??= []).add(range);
|
||||
}
|
||||
});
|
||||
|
||||
assert(dfsOrder.length ==
|
||||
(concreteClassCount + abstractClassCount + anonymousMixinClassCount));
|
||||
(concreteClassCount +
|
||||
abstractClassCount +
|
||||
anonymousMixinClassCount +
|
||||
alreadyAssignedCount));
|
||||
|
||||
return ClassIdNumbering._(
|
||||
translator,
|
||||
subclasses,
|
||||
implementors,
|
||||
concreteSubclassRange,
|
||||
concreteSubclassRanges,
|
||||
concreteSubclassRangesForDynamicModule,
|
||||
masqueraded,
|
||||
dfsOrder,
|
||||
classIds,
|
||||
firstClassId + concreteClassCount - 1,
|
||||
firstClassId + concreteClassCount + abstractClassCount - 1);
|
||||
savedMaxConcreteClassId ?? nextConcreteClassId - 1,
|
||||
savedMaxClassId ?? nextAbstractClassId - 1,
|
||||
savedMaxConcreteClassId == null ? null : nextConcreteClassId - 1,
|
||||
savedMaxClassId == null ? null : nextAbstractClassId - 1);
|
||||
}
|
||||
|
||||
Range getConcreteSubclassRange(Class klass) =>
|
||||
_concreteSubclassIdRange[klass]!;
|
||||
List<Range> getConcreteSubclassRanges(Class klass) =>
|
||||
_concreteSubclassIdRange[klass] ?? const [];
|
||||
}
|
||||
|
||||
sealed class ClassId {
|
||||
int get _localValue;
|
||||
}
|
||||
|
||||
final class AbsoluteClassId extends ClassId {
|
||||
final int value;
|
||||
|
||||
@override
|
||||
int get _localValue => value;
|
||||
|
||||
AbsoluteClassId(this.value);
|
||||
}
|
||||
|
||||
final class RelativeClassId extends ClassId {
|
||||
final int relativeValue;
|
||||
@override
|
||||
int get _localValue => relativeValue;
|
||||
|
||||
RelativeClassId(this.relativeValue);
|
||||
}
|
||||
|
||||
// A range of class ids, both ends inclusive.
|
||||
|
||||
@@ -414,6 +414,8 @@ class ClosureLayouter extends RecursiveVisitor {
|
||||
ClosureRepresentation? parent,
|
||||
Map<NameCombination, int>? indexOfCombination,
|
||||
Iterable<int> paramCounts) {
|
||||
// TODO(natebiggs): Add logic to allow for changing signatures in a dynamic
|
||||
// module.
|
||||
List<String> nameTags = ["$typeCount", "$positionalCount", ...names];
|
||||
String vtableName = ["#Vtable", ...nameTags].join("-");
|
||||
String closureName = ["#Closure", ...nameTags].join("-");
|
||||
@@ -723,7 +725,7 @@ class ClosureLayouter extends RecursiveVisitor {
|
||||
w.Local typeParam(int i) => instantiationFunction.locals[1 + i];
|
||||
|
||||
// Header for the closure struct
|
||||
b.pushObjectHeaderFields(translator.closureInfo);
|
||||
b.pushObjectHeaderFields(translator, translator.closureInfo);
|
||||
|
||||
// Context for the instantiated closure, containing the original closure and
|
||||
// the type arguments
|
||||
|
||||
@@ -1542,9 +1542,15 @@ abstract class AstCodeGenerator
|
||||
}
|
||||
|
||||
Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
|
||||
return translator.hierarchy.getDispatchTarget(
|
||||
final staticTarget = translator.hierarchy.getDispatchTarget(
|
||||
enclosingMember.enclosingClass!.superclass!, interfaceTarget.name,
|
||||
setter: setter)!;
|
||||
setter: setter);
|
||||
if (staticTarget != null) return staticTarget;
|
||||
|
||||
// During dynamic module compilation a mixin might include a super call to
|
||||
// an abstract class with no implementations yet.
|
||||
assert(translator.dynamicModuleSupportEnabled);
|
||||
return interfaceTarget;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1868,33 +1874,46 @@ abstract class AstCodeGenerator
|
||||
|
||||
pushReceiver(selector.signature);
|
||||
|
||||
final targets = selector.targets(unchecked: useUncheckedEntry);
|
||||
SelectorTargets targets;
|
||||
|
||||
if (targets.targetRanges.length == 1) {
|
||||
// TODO(natebiggs): Ensure dynamic modules exclude this.
|
||||
|
||||
assert(targets.staticDispatchRanges.length == 1);
|
||||
final target = targets.targetRanges.single.target;
|
||||
final signature = translator.signatureForDirectCall(target);
|
||||
final paramInfo = translator.paramInfoForDirectCall(target);
|
||||
pushArguments(signature, paramInfo);
|
||||
return translator.outputOrVoid(call(target));
|
||||
if (!translator.dynamicModuleSupportEnabled ||
|
||||
b.module == translator.mainModule) {
|
||||
targets =
|
||||
selector.targets(unchecked: useUncheckedEntry, dynamicModule: false);
|
||||
} else {
|
||||
targets =
|
||||
selector.targets(unchecked: useUncheckedEntry, dynamicModule: true);
|
||||
}
|
||||
|
||||
if (targets.targetRanges.isEmpty) {
|
||||
// TODO(natebiggs): Ensure dynamic modules exclude this.
|
||||
|
||||
// Unreachable call
|
||||
b.comment("Virtual call of $name with no targets"
|
||||
" at ${node.location}");
|
||||
pushArguments(selector.signature, selector.paramInfo);
|
||||
for (int i = 0; i < selector.signature.inputs.length; ++i) {
|
||||
b.drop();
|
||||
final isDynamicModuleOverrideable = selector.isDynamicModuleOverrideable;
|
||||
if (!isDynamicModuleOverrideable) {
|
||||
w.ValueType? checkRanges(List<({Range range, Reference target})> ranges) {
|
||||
if (ranges.length == 1) {
|
||||
final target = translator.getFunctionEntry(ranges[0].target,
|
||||
uncheckedEntry: useUncheckedEntry);
|
||||
final signature = translator.signatureForDirectCall(target);
|
||||
final paramInfo = translator.paramInfoForDirectCall(target);
|
||||
pushArguments(signature, paramInfo);
|
||||
return translator.outputOrVoid(call(target));
|
||||
}
|
||||
if (ranges.isEmpty) {
|
||||
// Unreachable call
|
||||
b.comment("Virtual call of $name with no targets"
|
||||
" at ${node.location}");
|
||||
pushArguments(selector.signature, selector.paramInfo);
|
||||
for (int i = 0; i < selector.signature.inputs.length; ++i) {
|
||||
b.drop();
|
||||
}
|
||||
b.block(const [], selector.signature.outputs);
|
||||
b.unreachable();
|
||||
b.end();
|
||||
return translator.outputOrVoid(selector.signature.outputs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
b.block(const [], selector.signature.outputs);
|
||||
b.unreachable();
|
||||
b.end();
|
||||
return translator.outputOrVoid(selector.signature.outputs);
|
||||
|
||||
final result = checkRanges(targets.targetRanges);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
// Receiver is already on stack.
|
||||
@@ -1904,8 +1923,7 @@ abstract class AstCodeGenerator
|
||||
pushArguments(selector.signature, selector.paramInfo);
|
||||
|
||||
if (targets.staticDispatchRanges.isNotEmpty) {
|
||||
// TODO(natebiggs): Ensure dynamic modules exclude this.
|
||||
|
||||
assert(!translator.dynamicModuleSupportEnabled);
|
||||
b.invoke(translator
|
||||
.getPolymorphicDispatchersForModule(b.module)
|
||||
.getPolymorphicDispatcher(selector,
|
||||
@@ -1914,6 +1932,7 @@ abstract class AstCodeGenerator
|
||||
b.comment("Instance $kind of '$name'");
|
||||
b.local_get(receiverVar);
|
||||
translator.callDispatchTable(b, selector,
|
||||
interfaceTarget: interfaceTarget,
|
||||
useUncheckedEntry: useUncheckedEntry);
|
||||
}
|
||||
|
||||
@@ -2020,7 +2039,7 @@ abstract class AstCodeGenerator
|
||||
if (target is Procedure && !target.isGetter) {
|
||||
// Super tear-off
|
||||
w.StructType closureStruct = _pushClosure(
|
||||
translator.getTearOffClosure(target),
|
||||
translator.getTearOffClosure(target, b.module),
|
||||
translator.getTearOffType(target),
|
||||
() => visitThis(w.RefType.struct(nullable: false)));
|
||||
return w.RefType.def(closureStruct, nullable: false);
|
||||
@@ -2321,6 +2340,7 @@ abstract class AstCodeGenerator
|
||||
ClosureImplementation closure = translator.getClosure(
|
||||
functionNode,
|
||||
lambda.function,
|
||||
b.module,
|
||||
ParameterInfo.fromLocalFunction(functionNode),
|
||||
"closure wrapper at ${functionNode.location}");
|
||||
return _pushClosure(
|
||||
@@ -2336,7 +2356,7 @@ abstract class AstCodeGenerator
|
||||
ClassInfo info = translator.closureInfo;
|
||||
translator.functions.recordClassAllocation(info.classId);
|
||||
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
pushContext();
|
||||
translator.globals.readGlobal(b, closure.vtable);
|
||||
types.makeType(this, functionType);
|
||||
@@ -2947,7 +2967,7 @@ abstract class AstCodeGenerator
|
||||
translator.getRecordClassInfo(node.recordType);
|
||||
translator.functions.recordClassAllocation(recordClassInfo.classId);
|
||||
|
||||
b.pushObjectHeaderFields(recordClassInfo);
|
||||
b.pushObjectHeaderFields(translator, recordClassInfo);
|
||||
for (Expression positional in node.positional) {
|
||||
translateExpression(positional, translator.topInfo.nullableType);
|
||||
}
|
||||
@@ -3351,13 +3371,14 @@ class TearOffCodeGenerator extends AstCodeGenerator {
|
||||
_initializeThis(member.reference);
|
||||
Procedure procedure = member as Procedure;
|
||||
DartType functionType = translator.getTearOffType(procedure);
|
||||
ClosureImplementation closure = translator.getTearOffClosure(procedure);
|
||||
ClosureImplementation closure =
|
||||
translator.getTearOffClosure(procedure, b.module);
|
||||
w.StructType struct = closure.representation.closureStruct;
|
||||
|
||||
ClassInfo info = translator.closureInfo;
|
||||
translator.functions.recordClassAllocation(info.classId);
|
||||
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
b.local_get(paramLocals[0]); // `this` as context
|
||||
// The closure requires a struct value so box `this` if necessary.
|
||||
translator.convertType(b, paramLocals[0].type,
|
||||
@@ -3837,7 +3858,7 @@ class ConstructorAllocatorCodeGenerator extends AstCodeGenerator {
|
||||
}
|
||||
|
||||
// Set field values
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
|
||||
for (w.Local local in orderedFieldLocals.reversed) {
|
||||
b.local_get(local);
|
||||
@@ -4400,6 +4421,80 @@ extension MacroAssembler on w.InstructionsBuilder {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
void incrementingLoop(
|
||||
{required void Function() pushStart,
|
||||
required void Function() pushLimit,
|
||||
required void Function(w.Local) genBody,
|
||||
int step = 1}) {
|
||||
final endLoop = block();
|
||||
final limitVar = addLocal(w.NumType.i32);
|
||||
final loopVar = addLocal(w.NumType.i32);
|
||||
pushLimit();
|
||||
local_set(limitVar);
|
||||
pushStart();
|
||||
local_set(loopVar);
|
||||
|
||||
final loopLabel = loop();
|
||||
local_get(loopVar);
|
||||
local_get(limitVar);
|
||||
i32_ge_u();
|
||||
br_if(endLoop);
|
||||
|
||||
genBody(loopVar);
|
||||
local_get(loopVar);
|
||||
i32_const(step);
|
||||
i32_add();
|
||||
local_set(loopVar);
|
||||
br(loopLabel);
|
||||
end();
|
||||
end();
|
||||
}
|
||||
|
||||
/// [ref Array] [ref Array] -> [ref Array]
|
||||
///
|
||||
/// Takes the two arrays on the stack and concatenates them into a single
|
||||
/// array. They both must have the same type provided as [arrayRefType].
|
||||
/// Uses [pushDefaultElement] as the filler element that holds space in the
|
||||
/// array until values are copied over.
|
||||
void concatenateWasmArrays(w.ArrayType arrayType,
|
||||
{required void Function(
|
||||
w.InstructionsBuilder b, w.Local oldArray, w.Local newArray)
|
||||
pushDefaultElement}) {
|
||||
final arrayRefType = w.RefType(arrayType, nullable: false);
|
||||
final newArray = addLocal(arrayRefType);
|
||||
final oldArray = addLocal(arrayRefType);
|
||||
final newArrayLen = addLocal(w.NumType.i32);
|
||||
final oldArrayLen = addLocal(w.NumType.i32);
|
||||
final joinedArray = addLocal(arrayRefType);
|
||||
|
||||
local_set(newArray);
|
||||
local_set(oldArray);
|
||||
pushDefaultElement(this, oldArray, newArray);
|
||||
local_get(newArray);
|
||||
array_len();
|
||||
local_set(newArrayLen);
|
||||
local_get(oldArray);
|
||||
array_len();
|
||||
local_tee(oldArrayLen);
|
||||
local_get(newArrayLen);
|
||||
i32_add();
|
||||
array_new(arrayType);
|
||||
local_tee(joinedArray);
|
||||
i32_const(0);
|
||||
local_get(oldArray);
|
||||
i32_const(0);
|
||||
local_get(oldArrayLen);
|
||||
array_copy(arrayType, arrayType);
|
||||
local_get(joinedArray);
|
||||
local_get(oldArrayLen);
|
||||
local_get(newArray);
|
||||
i32_const(0);
|
||||
local_get(newArrayLen);
|
||||
array_copy(arrayType, arrayType);
|
||||
local_get(joinedArray);
|
||||
end();
|
||||
}
|
||||
|
||||
/// `[i32] -> [i32]`
|
||||
///
|
||||
/// Consumes a `i32` class ID, leaves an `i32` as `bool` for whether
|
||||
@@ -4733,11 +4828,21 @@ extension MacroAssembler on w.InstructionsBuilder {
|
||||
}
|
||||
|
||||
/// Pushes fields common to all Dart objects (class id, id hash).
|
||||
void pushObjectHeaderFields(ClassInfo classInfo) {
|
||||
// TODO(natebiggs): Adjust class ID for dynamic module if appropriate.
|
||||
i32_const(classInfo.classId);
|
||||
void pushObjectHeaderFields(Translator translator, ClassInfo classInfo) {
|
||||
pushClassIdToStack(translator, classInfo.classId);
|
||||
i32_const(initialIdentityHash);
|
||||
}
|
||||
|
||||
void pushClassIdToStack(Translator translator, ClassId classId) {
|
||||
switch (classId) {
|
||||
case AbsoluteClassId():
|
||||
i32_const(classId.value);
|
||||
case RelativeClassId():
|
||||
i32_const(classId.relativeValue);
|
||||
translator.pushModuleId(this);
|
||||
translator.callReference(translator.globalizeClassId.reference, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A call target that may be called with a direct call or may be inlined.
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// 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' show File;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:build_integration/file_system/multi_root.dart'
|
||||
show MultiRootFileSystem;
|
||||
show MultiRootFileSystem, MultiRootFileSystemEntity;
|
||||
import 'package:front_end/src/api_prototype/standard_file_system.dart'
|
||||
show StandardFileSystem;
|
||||
import 'package:front_end/src/api_unstable/vm.dart'
|
||||
@@ -19,12 +20,14 @@ import 'package:front_end/src/api_unstable/vm.dart'
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/kernel.dart' show writeComponentToText;
|
||||
import 'package:kernel/kernel.dart'
|
||||
show writeComponentToBinary, writeComponentToText;
|
||||
import 'package:kernel/library_index.dart';
|
||||
import 'package:kernel/verifier.dart';
|
||||
import 'package:path/path.dart' as path show setExtension;
|
||||
import 'package:vm/kernel_front_end.dart' show writeDepfile;
|
||||
import 'package:vm/transformations/mixin_deduplication.dart'
|
||||
as mixin_deduplication show transformComponent;
|
||||
as mixin_deduplication show transformLibraries;
|
||||
import 'package:vm/transformations/to_string_transformer.dart'
|
||||
as to_string_transformer;
|
||||
import 'package:vm/transformations/type_flow/transformer.dart' as globalTypeFlow
|
||||
@@ -35,10 +38,14 @@ import 'package:wasm_builder/wasm_builder.dart' show Serializer;
|
||||
|
||||
import 'compiler_options.dart' as compiler;
|
||||
import 'constant_evaluator.dart';
|
||||
import 'deferred_loading.dart' as deferred_loading;
|
||||
import 'deferred_loading.dart';
|
||||
import 'dynamic_module_kernel_metadata.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'js/runtime_generator.dart' as js;
|
||||
import 'modules.dart';
|
||||
import 'record_class_generator.dart';
|
||||
import 'records.dart';
|
||||
import 'serialization.dart';
|
||||
import 'target.dart' as wasm show Mode;
|
||||
import 'target.dart' hide Mode;
|
||||
import 'translator.dart';
|
||||
@@ -47,7 +54,7 @@ sealed class CompilationResult {}
|
||||
|
||||
class CompilationSuccess extends CompilationResult {
|
||||
final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
|
||||
final String jsRuntime;
|
||||
final String? jsRuntime;
|
||||
final String supportJs;
|
||||
|
||||
CompilationSuccess(this.wasmModules, this.jsRuntime, this.supportJs);
|
||||
@@ -138,15 +145,50 @@ Future<CompilationResult> compileToModule(
|
||||
StandardFileSystem.instance);
|
||||
}
|
||||
|
||||
Future<Uri?> resolveUri(Uri? uri) async {
|
||||
if (uri == null) return null;
|
||||
var fileSystemEntity = compilerOptions.fileSystem.entityForUri(uri);
|
||||
if (fileSystemEntity is MultiRootFileSystemEntity) {
|
||||
fileSystemEntity = await fileSystemEntity.delegate;
|
||||
}
|
||||
return fileSystemEntity.uri;
|
||||
}
|
||||
|
||||
if (options.platformPath != null) {
|
||||
compilerOptions.sdkSummary = options.platformPath;
|
||||
} else {
|
||||
compilerOptions.compileSdk = true;
|
||||
}
|
||||
|
||||
DynamicModuleMetadata? dynamicModuleMetadata;
|
||||
final dynamicMainModuleUri = options.dynamicModuleMainUri;
|
||||
if (dynamicMainModuleUri != null && options.dynamicInterfaceUri == null) {
|
||||
final filename = options.dynamicModuleMetadataFile ??
|
||||
Uri.parse(
|
||||
path.setExtension(dynamicMainModuleUri.toFilePath(), '.dyndata'));
|
||||
final dynamicModuleMetadataBytes =
|
||||
await File.fromUri(filename).readAsBytes();
|
||||
final source = BinaryDataSource(dynamicModuleMetadataBytes);
|
||||
dynamicModuleMetadata = DynamicModuleMetadata.deserialize(source);
|
||||
}
|
||||
|
||||
final dynamicModuleMainUri = await resolveUri(options.dynamicModuleMainUri);
|
||||
final dynamicInterfaceUri = await resolveUri(options.dynamicInterfaceUri);
|
||||
final isDynamicMainModule =
|
||||
dynamicModuleMainUri != null && dynamicInterfaceUri != null;
|
||||
final isDynamicModule =
|
||||
dynamicModuleMainUri != null && dynamicInterfaceUri == null;
|
||||
if (isDynamicModule) {
|
||||
dynamicModuleMetadata!.verifyDynamicModuleOptions(options);
|
||||
compilerOptions.additionalDills.add(dynamicModuleMainUri);
|
||||
} else if (isDynamicMainModule) {
|
||||
DynamicModuleMetadata.verifyMainModuleOptions(options);
|
||||
}
|
||||
|
||||
CompilerResult? compilerResult;
|
||||
try {
|
||||
compilerResult = await kernelForProgram(options.mainUri, compilerOptions);
|
||||
compilerResult = await kernelForProgram(options.mainUri, compilerOptions,
|
||||
requireMain: !isDynamicModule);
|
||||
} catch (e, s) {
|
||||
return CFECrashError(e, s);
|
||||
}
|
||||
@@ -183,18 +225,38 @@ Future<CompilationResult> compileToModule(
|
||||
component, options.deleteToStringPackageUri);
|
||||
}
|
||||
|
||||
if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
deferred_loading.transformComponentForTestMode(
|
||||
component, classHierarchy, coreTypes, target);
|
||||
ModuleStrategy moduleStrategy;
|
||||
if (options.translatorOptions.enableDeferredLoading) {
|
||||
moduleStrategy =
|
||||
DeferredLoadingModuleStrategy(component, options, target, coreTypes);
|
||||
} else if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
moduleStrategy =
|
||||
StressTestModuleStrategy(component, coreTypes, target, classHierarchy);
|
||||
} else if (isDynamicMainModule) {
|
||||
moduleStrategy = DynamicMainModuleStrategy(
|
||||
component,
|
||||
coreTypes,
|
||||
classHierarchy,
|
||||
File.fromUri(dynamicInterfaceUri).readAsStringSync(),
|
||||
options.dynamicInterfaceUri!);
|
||||
} else if (isDynamicModule) {
|
||||
moduleStrategy = DynamicModuleStrategy(component, options, target,
|
||||
coreTypes, classHierarchy, dynamicModuleMainUri);
|
||||
} else {
|
||||
moduleStrategy = DefaultModuleStrategy(component);
|
||||
}
|
||||
|
||||
final librariesToTransform = isDynamicModule
|
||||
? component.getMainModuleLibraries(coreTypes)
|
||||
: component.libraries;
|
||||
ConstantEvaluator constantEvaluator = ConstantEvaluator(
|
||||
options, target, component, coreTypes, classHierarchy, libraryIndex);
|
||||
unreachable_code_elimination.transformComponent(target, component,
|
||||
unreachable_code_elimination.transformLibraries(target, librariesToTransform,
|
||||
constantEvaluator, options.translatorOptions.enableAsserts);
|
||||
|
||||
js.RuntimeFinalizer jsRuntimeFinalizer =
|
||||
js.createRuntimeFinalizer(component, coreTypes, classHierarchy);
|
||||
js.RuntimeFinalizer? jsRuntimeFinalizer = isDynamicModule
|
||||
? null
|
||||
: js.createRuntimeFinalizer(component, coreTypes, classHierarchy);
|
||||
|
||||
final Map<RecordShape, Class> recordClasses =
|
||||
generateRecordClasses(component, coreTypes);
|
||||
@@ -204,7 +266,14 @@ Future<CompilationResult> compileToModule(
|
||||
writeComponentToText(component, path: options.dumpKernelBeforeTfa!);
|
||||
}
|
||||
|
||||
mixin_deduplication.transformComponent(component);
|
||||
mixin_deduplication.transformLibraries(librariesToTransform);
|
||||
|
||||
moduleStrategy.prepareComponent();
|
||||
|
||||
if (isDynamicMainModule) {
|
||||
writeComponentToBinary(component, dynamicModuleMainUri.path,
|
||||
includeSource: false);
|
||||
}
|
||||
|
||||
// Patch `dart:_internal`s `mainTearOff` getter.
|
||||
final internalLib = component.libraries
|
||||
@@ -217,6 +286,7 @@ Future<CompilationResult> compileToModule(
|
||||
|
||||
// Keep the flags in-sync with
|
||||
// pkg/vm/test/transformations/type_flow/transformer_test.dart
|
||||
// TODO(natebiggs): Only run TFA on main module when dynamic modules enabled.
|
||||
globalTypeFlow.transformComponent(target, coreTypes, component,
|
||||
useRapidTypeAnalysis: false);
|
||||
|
||||
@@ -231,11 +301,12 @@ Future<CompilationResult> compileToModule(
|
||||
return true;
|
||||
}());
|
||||
|
||||
final moduleOutputData = deferred_loading.modulesForComponent(
|
||||
component, options, target, coreTypes);
|
||||
final moduleOutputData = moduleStrategy.buildModuleOutputData();
|
||||
|
||||
var translator = Translator(component, coreTypes, libraryIndex, recordClasses,
|
||||
moduleOutputData, options.translatorOptions);
|
||||
moduleOutputData, options.translatorOptions,
|
||||
dynamicModuleMetadata: dynamicModuleMetadata,
|
||||
enableDynamicModules: dynamicModuleMainUri != null);
|
||||
|
||||
String? depFile = options.depFile;
|
||||
if (depFile != null) {
|
||||
@@ -247,6 +318,7 @@ Future<CompilationResult> compileToModule(
|
||||
final modules = translator.translate(sourceMapUrlGenerator);
|
||||
final wasmModules = <String, ({Uint8List moduleBytes, String? sourceMap})>{};
|
||||
modules.forEach((moduleOutput, module) {
|
||||
if (moduleOutput.skipEmit) return;
|
||||
final serializer = Serializer();
|
||||
module.serialize(serializer);
|
||||
final wasmModuleSerialized = serializer.data;
|
||||
@@ -257,13 +329,22 @@ Future<CompilationResult> compileToModule(
|
||||
(moduleBytes: wasmModuleSerialized, sourceMap: sourceMap);
|
||||
});
|
||||
|
||||
String jsRuntime = jsRuntimeFinalizer.generate(
|
||||
final jsRuntime = jsRuntimeFinalizer?.generate(
|
||||
translator.functions.translatedProcedures,
|
||||
translator.internalizedStringsForJSRuntime,
|
||||
translator.options.requireJsStringBuiltin,
|
||||
mode);
|
||||
|
||||
final supportJs = _generateSupportJs(options.translatorOptions);
|
||||
if (isDynamicMainModule) {
|
||||
final filename = options.dynamicModuleMetadataFile ??
|
||||
Uri.parse(
|
||||
path.setExtension(dynamicMainModuleUri!.toFilePath(), '.dyndata'));
|
||||
final sink = BinaryDataSink();
|
||||
translator.dynamicModuleInfo!.toMetadata(options).serialize(sink);
|
||||
await File.fromUri(filename).writeAsBytes(sink.takeBytes());
|
||||
}
|
||||
|
||||
return CompilationSuccess(wasmModules, jsRuntime, supportJs);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ class ConstantEvaluator extends kernel.ConstantEvaluator
|
||||
implements VMConstantEvaluator {
|
||||
final bool _checkBounds;
|
||||
final bool _minify;
|
||||
final bool _hasDynamicModuleSupport;
|
||||
|
||||
final Procedure _dartInternalCheckBoundsGetter;
|
||||
final Procedure _dartInternalMinifyGetter;
|
||||
final Procedure _dartInternalHasDynamicModuleSupportGetter;
|
||||
|
||||
ConstantEvaluator(
|
||||
WasmCompilerOptions options,
|
||||
@@ -30,10 +32,14 @@ class ConstantEvaluator extends kernel.ConstantEvaluator
|
||||
LibraryIndex libraryIndex)
|
||||
: _checkBounds = !options.translatorOptions.omitBoundsChecks,
|
||||
_minify = options.translatorOptions.minify,
|
||||
_hasDynamicModuleSupport = options.dynamicModuleMainUri != null,
|
||||
_dartInternalCheckBoundsGetter = libraryIndex.getTopLevelProcedure(
|
||||
"dart:_internal", "get:checkBounds"),
|
||||
_dartInternalMinifyGetter =
|
||||
libraryIndex.getTopLevelProcedure("dart:_internal", "get:minify"),
|
||||
_dartInternalHasDynamicModuleSupportGetter =
|
||||
libraryIndex.getTopLevelProcedure(
|
||||
"dart:_internal", "get:hasDynamicModuleSupport"),
|
||||
super(
|
||||
target.dartLibrarySupport,
|
||||
target.constantsBackend,
|
||||
@@ -56,6 +62,9 @@ class ConstantEvaluator extends kernel.ConstantEvaluator
|
||||
if (target == _dartInternalMinifyGetter) {
|
||||
return canonicalize(BoolConstant(_minify));
|
||||
}
|
||||
if (target == _dartInternalHasDynamicModuleSupportGetter) {
|
||||
return canonicalize(BoolConstant(_hasDynamicModuleSupport));
|
||||
}
|
||||
|
||||
return super.visitStaticGet(node);
|
||||
}
|
||||
@@ -67,5 +76,6 @@ class ConstantEvaluator extends kernel.ConstantEvaluator
|
||||
@override
|
||||
bool shouldEvaluateMember(Member node) =>
|
||||
node == _dartInternalCheckBoundsGetter ||
|
||||
node == _dartInternalMinifyGetter;
|
||||
node == _dartInternalMinifyGetter ||
|
||||
node == _dartInternalHasDynamicModuleSupportGetter;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
import 'class_info.dart';
|
||||
import 'closures.dart';
|
||||
import 'code_generator.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'param_info.dart';
|
||||
import 'translator.dart';
|
||||
import 'types.dart';
|
||||
@@ -69,6 +70,7 @@ typedef ConstantCodeGenerator = void Function(w.InstructionsBuilder);
|
||||
class Constants {
|
||||
final Translator translator;
|
||||
final Map<Constant, ConstantInfo> constantInfo = {};
|
||||
final Map<Constant, ConstantInfo> dynamicModuleConstantInfo = {};
|
||||
w.DataSegmentBuilder? oneByteStringSegment;
|
||||
w.DataSegmentBuilder? twoByteStringSegment;
|
||||
late final ClassInfo typeInfo = translator.classInfo[translator.typeClass]!;
|
||||
@@ -114,6 +116,11 @@ class Constants {
|
||||
{translator.wasmI32Value.fieldReference: IntConstant(value)});
|
||||
}
|
||||
|
||||
// Used as an indicator for interface types that the enclosed class ID must be
|
||||
// globalized on instantiation. Resolves to a normal _InterfaceType.
|
||||
static final Class _relativeInterfaceTypeIndicator =
|
||||
Class(name: '', fileUri: Uri());
|
||||
|
||||
/// Makes a `WasmArray<_Type>` [InstanceConstant].
|
||||
InstanceConstant makeTypeArray(Iterable<DartType> types) {
|
||||
return makeArrayOf(
|
||||
@@ -160,8 +167,8 @@ class Constants {
|
||||
/// Sub-constants must have Wasm globals assigned before the global for the
|
||||
/// composite constant is assigned, since global initializers can only refer
|
||||
/// to earlier globals.
|
||||
ConstantInfo? ensureConstant(Constant constant) {
|
||||
return ConstantCreator(this).ensureConstant(constant);
|
||||
ConstantInfo? ensureConstant(Constant constant, w.ModuleBuilder module) {
|
||||
return ConstantCreator(this, module).ensureConstant(constant);
|
||||
}
|
||||
|
||||
/// Emit code to push a constant onto the stack.
|
||||
@@ -228,9 +235,22 @@ class Constants {
|
||||
}
|
||||
|
||||
InstanceConstant _makeInterfaceTypeConstant(InterfaceType type) {
|
||||
return _makeTypeConstant(translator.interfaceTypeClass, type.nullability, {
|
||||
translator.interfaceTypeClassIdField.fieldReference:
|
||||
makeWasmI32(translator.classIdNumbering.classIds[type.classNode]!),
|
||||
final wrappedClassId =
|
||||
translator.classIdNumbering.classIds[type.classNode]!;
|
||||
final (typeClass, classId) = switch (wrappedClassId) {
|
||||
RelativeClassId() => (
|
||||
_relativeInterfaceTypeIndicator,
|
||||
wrappedClassId.relativeValue
|
||||
),
|
||||
AbsoluteClassId() => (
|
||||
translator.interfaceTypeClass,
|
||||
wrappedClassId.value
|
||||
),
|
||||
};
|
||||
// If the class ID is relative we will detect that when the constant is
|
||||
// emitted and adjust it accordingly.
|
||||
return _makeTypeConstant(typeClass, type.nullability, {
|
||||
translator.interfaceTypeClassIdField.fieldReference: makeWasmI32(classId),
|
||||
translator.interfaceTypeTypeArguments.fieldReference:
|
||||
makeTypeArray(type.typeArguments),
|
||||
});
|
||||
@@ -354,7 +374,8 @@ class ConstantInstantiator extends ConstantVisitor<w.ValueType>
|
||||
|
||||
@override
|
||||
w.ValueType defaultConstant(Constant constant) {
|
||||
ConstantInfo info = ConstantCreator(constants).ensureConstant(constant)!;
|
||||
ConstantInfo info =
|
||||
ConstantCreator(constants, b.module).ensureConstant(constant)!;
|
||||
return info.readConstant(translator, b);
|
||||
}
|
||||
|
||||
@@ -375,6 +396,10 @@ class ConstantInstantiator extends ConstantVisitor<w.ValueType>
|
||||
|
||||
@override
|
||||
w.ValueType visitNullConstant(NullConstant node) {
|
||||
if (expectedType == w.RefType.func(nullable: true)) {
|
||||
b.ref_null((expectedType as w.RefType).heapType);
|
||||
return expectedType;
|
||||
}
|
||||
b.ref_null(w.HeapType.none);
|
||||
return const w.RefType.none(nullable: true);
|
||||
}
|
||||
@@ -435,8 +460,12 @@ class ConstantInstantiator extends ConstantVisitor<w.ValueType>
|
||||
class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
with ConstantVisitorDefaultMixin<ConstantInfo?> {
|
||||
final Constants constants;
|
||||
final w.ModuleBuilder targetModule;
|
||||
|
||||
ConstantCreator(this.constants);
|
||||
ConstantCreator(this.constants, w.ModuleBuilder module)
|
||||
: targetModule = constants.translator.isDynamicModule
|
||||
? module
|
||||
: constants.translator.mainModule;
|
||||
|
||||
Translator get translator => constants.translator;
|
||||
Types get types => translator.types;
|
||||
@@ -453,11 +482,15 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
constant = constants._lowerTypeConstant(type);
|
||||
}
|
||||
|
||||
ConstantInfo? info = constants.constantInfo[constant];
|
||||
final cache = translator.dynamicModuleSupportEnabled &&
|
||||
!translator.isMainModule(targetModule)
|
||||
? constants.dynamicModuleConstantInfo
|
||||
: constants.constantInfo;
|
||||
ConstantInfo? info = cache[constant];
|
||||
if (info == null) {
|
||||
info = constant.accept(this);
|
||||
if (info != null) {
|
||||
constants.constantInfo[constant] = info;
|
||||
cache[constant] = info;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
@@ -467,18 +500,21 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
Constant constant, w.RefType type, ConstantCodeGenerator generator,
|
||||
{bool lazy = false}) {
|
||||
assert(!type.nullable);
|
||||
final mainModule = translator.mainModule;
|
||||
if (lazy) {
|
||||
if (lazy || translator.dynamicModuleSupportEnabled) {
|
||||
// Create uninitialized global and function to initialize it.
|
||||
final global =
|
||||
mainModule.globals.define(w.GlobalType(type.withNullability(true)));
|
||||
targetModule.globals.define(w.GlobalType(type.withNullability(true)));
|
||||
global.initializer.ref_null(w.HeapType.none);
|
||||
global.initializer.end();
|
||||
w.FunctionType ftype =
|
||||
translator.typesBuilder.defineFunction(const [], [type]);
|
||||
final function = mainModule.functions.define(ftype, "$constant");
|
||||
final function = targetModule.functions.define(ftype, "$constant");
|
||||
final b2 = function.body;
|
||||
generator(b2);
|
||||
if (translator.dynamicModuleSupportEnabled) {
|
||||
final valueLocal = b2.addLocal(type);
|
||||
constant.accept(ConstantCanonicalizer(translator, b2, valueLocal));
|
||||
}
|
||||
w.Local temp = b2.addLocal(type);
|
||||
b2.local_tee(temp);
|
||||
b2.global_set(global);
|
||||
@@ -491,7 +527,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
assert(!constants.currentlyCreating);
|
||||
constants.currentlyCreating = true;
|
||||
final global =
|
||||
mainModule.globals.define(w.GlobalType(type, mutable: false));
|
||||
targetModule.globals.define(w.GlobalType(type, mutable: false));
|
||||
generator(global.initializer);
|
||||
global.initializer.end();
|
||||
constants.currentlyCreating = false;
|
||||
@@ -507,7 +543,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
ConstantInfo? visitBoolConstant(BoolConstant constant) {
|
||||
ClassInfo info = translator.classInfo[translator.boxedBoolClass]!;
|
||||
return createConstant(constant, info.nonNullableType, (b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const((info.classId as AbsoluteClassId).value);
|
||||
b.i32_const(constant.value ? 1 : 0);
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
@@ -517,7 +553,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
ConstantInfo? visitIntConstant(IntConstant constant) {
|
||||
ClassInfo info = translator.classInfo[translator.boxedIntClass]!;
|
||||
return createConstant(constant, info.nonNullableType, (b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const((info.classId as AbsoluteClassId).value);
|
||||
b.i64_const(constant.value);
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
@@ -527,7 +563,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
ConstantInfo? visitDoubleConstant(DoubleConstant constant) {
|
||||
ClassInfo info = translator.classInfo[translator.boxedDoubleClass]!;
|
||||
return createConstant(constant, info.nonNullableType, (b) {
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const((info.classId as AbsoluteClassId).value);
|
||||
b.f64_const(constant.value);
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
@@ -538,7 +574,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
if (translator.options.jsCompatibility) {
|
||||
ClassInfo info = translator.classInfo[translator.jsStringClass]!;
|
||||
return createConstant(constant, info.nonNullableType, (b) {
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
translator.globals.readGlobal(b,
|
||||
translator.getInternalizedStringGlobal(b.module, constant.value));
|
||||
b.struct_new(info.struct);
|
||||
@@ -556,19 +592,19 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
(info.struct.fields[FieldIndex.stringArray].type as w.RefType)
|
||||
.heapType as w.ArrayType;
|
||||
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
if (lazy) {
|
||||
// Initialize string contents from passive data segment.
|
||||
w.DataSegmentBuilder segment;
|
||||
Uint8List bytes;
|
||||
if (isOneByte) {
|
||||
segment = constants.oneByteStringSegment ??=
|
||||
translator.mainModule.dataSegments.define();
|
||||
targetModule.dataSegments.define();
|
||||
bytes = Uint8List.fromList(constant.value.codeUnits);
|
||||
} else {
|
||||
assert(Endian.host == Endian.little);
|
||||
segment = constants.twoByteStringSegment ??=
|
||||
translator.mainModule.dataSegments.define();
|
||||
targetModule.dataSegments.define();
|
||||
bytes = Uint16List.fromList(constant.value.codeUnits)
|
||||
.buffer
|
||||
.asUint8List();
|
||||
@@ -592,6 +628,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
@override
|
||||
ConstantInfo? visitInstanceConstant(InstanceConstant constant) {
|
||||
Class cls = constant.classNode;
|
||||
bool isRelativeInterfaceType = false;
|
||||
if (cls == translator.wasmArrayClass) {
|
||||
return _makeWasmArrayLiteral(constant, mutable: true);
|
||||
}
|
||||
@@ -602,17 +639,25 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cls == Constants._relativeInterfaceTypeIndicator) {
|
||||
cls = translator.interfaceTypeClass;
|
||||
isRelativeInterfaceType = true;
|
||||
}
|
||||
|
||||
ClassInfo info = translator.classInfo[cls]!;
|
||||
translator.functions.recordClassAllocation(info.classId);
|
||||
w.RefType type = info.nonNullableType;
|
||||
|
||||
// Collect sub-constants for field values.
|
||||
const int baseFieldCount = 2;
|
||||
int fieldCount = info.struct.fields.length;
|
||||
List<Constant?> subConstants = List.filled(fieldCount, null);
|
||||
bool lazy = false;
|
||||
// Relative class IDs will get adjusted at runtime based on the local
|
||||
// class ID base for the enclosing module. This must be done lazily
|
||||
// since the global is not const.
|
||||
bool lazy = isRelativeInterfaceType;
|
||||
constant.fieldValues.forEach((reference, subConstant) {
|
||||
int index = translator.fieldIndex[reference.asField]!;
|
||||
final field = reference.asField;
|
||||
int index = translator.fieldIndex[field]!;
|
||||
assert(subConstants[index] == null);
|
||||
subConstants[index] = subConstant;
|
||||
lazy |= ensureConstant(subConstant)?.isLazy ?? false;
|
||||
@@ -638,11 +683,16 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
}
|
||||
|
||||
return createConstant(constant, type, lazy: lazy, (b) {
|
||||
b.pushObjectHeaderFields(info);
|
||||
for (int i = baseFieldCount; i < fieldCount; i++) {
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
for (int i = FieldIndex.objectFieldBase; i < fieldCount; i++) {
|
||||
Constant subConstant = subConstants[i]!;
|
||||
constants.instantiateConstant(
|
||||
b, subConstant, info.struct.fields[i].type.unpacked);
|
||||
if (isRelativeInterfaceType && i == FieldIndex.interfaceTypeClassId) {
|
||||
assert(translator.isDynamicModule);
|
||||
translator.pushModuleId(b);
|
||||
translator.callReference(translator.globalizeClassId.reference, b);
|
||||
}
|
||||
}
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
@@ -746,7 +796,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
w.ArrayType arrayType = translator.listArrayType;
|
||||
w.ValueType elementType = arrayType.elementType.type.unpacked;
|
||||
int length = constant.entries.length;
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
constants.instantiateConstant(
|
||||
b, typeArgConstant, constants.typeInfo.nullableType);
|
||||
b.i64_const(length);
|
||||
@@ -853,24 +903,25 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
Constant functionTypeConstant =
|
||||
constants._lowerTypeConstant(translator.getTearOffType(member));
|
||||
ensureConstant(functionTypeConstant);
|
||||
ClosureImplementation closure = translator.getTearOffClosure(member);
|
||||
ClosureImplementation closure =
|
||||
translator.getTearOffClosure(member, targetModule);
|
||||
w.StructType struct = closure.representation.closureStruct;
|
||||
w.RefType type = w.RefType.def(struct, nullable: false);
|
||||
|
||||
// The vtable for the target will be stored on a global in the target's
|
||||
// module.
|
||||
final isLazy = !translator
|
||||
.isMainModule(translator.moduleForReference(constant.targetReference));
|
||||
final isLazy =
|
||||
translator.moduleForReference(constant.targetReference) != targetModule;
|
||||
// The dummy struct must be declared before the constant global so that the
|
||||
// constant's initializer can reference it.
|
||||
final dummyStructGlobal = translator
|
||||
.getDummyValuesCollectorForModule(translator.mainModule)
|
||||
.getDummyValuesCollectorForModule(targetModule)
|
||||
.dummyStructGlobal;
|
||||
return createConstant(constant, type, (b) {
|
||||
ClassInfo info = translator.closureInfo;
|
||||
translator.functions.recordClassAllocation(info.classId);
|
||||
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
translator.globals.readGlobal(b, dummyStructGlobal); // Dummy context
|
||||
translator.globals.readGlobal(b, closure.vtable);
|
||||
constants.instantiateConstant(
|
||||
@@ -896,7 +947,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
constants._lowerTypeConstant(instantiatedFunctionType);
|
||||
ensureConstant(functionTypeConstant);
|
||||
ClosureImplementation tearOffClosure =
|
||||
translator.getTearOffClosure(tearOffProcedure);
|
||||
translator.getTearOffClosure(tearOffProcedure, targetModule);
|
||||
int positionalCount = tearOffConstant.function.positionalParameters.length;
|
||||
List<String> names =
|
||||
tearOffConstant.function.namedParameters.map((p) => p.name!).toList();
|
||||
@@ -911,7 +962,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
final tearOffConstantInfo = ensureConstant(tearOffConstant)!;
|
||||
|
||||
w.BaseFunction makeDynamicCallEntry() {
|
||||
final function = translator.mainModule.functions.define(
|
||||
final function = targetModule.functions.define(
|
||||
translator.dynamicCallVtableEntryFunctionType, "dynamic call entry");
|
||||
|
||||
final b = function.body;
|
||||
@@ -1030,7 +1081,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
b.struct_new(instantiationOfTearOffRepresentation.vtableStruct);
|
||||
}
|
||||
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
|
||||
// Context is not used by the vtable functions, but it's needed for
|
||||
// closure equality checks to work (`_Closure._equals`).
|
||||
@@ -1062,7 +1113,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
StringConstant nameConstant = StringConstant(constant.name);
|
||||
bool lazy = ensureConstant(nameConstant)?.isLazy ?? false;
|
||||
return createConstant(constant, info.nonNullableType, lazy: lazy, (b) {
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
constants.instantiateConstant(b, nameConstant, stringType);
|
||||
b.struct_new(info.struct);
|
||||
});
|
||||
@@ -1084,7 +1135,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
|
||||
|
||||
return createConstant(constant, recordClassInfo.nonNullableType, lazy: lazy,
|
||||
(b) {
|
||||
b.pushObjectHeaderFields(recordClassInfo);
|
||||
b.pushObjectHeaderFields(translator, recordClassInfo);
|
||||
for (Constant argument in arguments) {
|
||||
constants.instantiateConstant(
|
||||
b, argument, translator.topInfo.nullableType);
|
||||
|
||||
@@ -6,62 +6,12 @@ import 'package:collection/collection.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
|
||||
import 'await_transformer.dart' as await_transformer;
|
||||
import 'compiler_options.dart';
|
||||
import 'generate_wasm.dart';
|
||||
import 'util.dart';
|
||||
|
||||
const _mainModuleId = 0;
|
||||
|
||||
Library? _enclosingLibraryForReference(Reference reference) {
|
||||
TreeNode? current = reference.node;
|
||||
// References generated for constants will not have a node attached.
|
||||
if (reference.node == null) return null;
|
||||
while (current != null) {
|
||||
if (current is Library) return current;
|
||||
current = current.parent;
|
||||
}
|
||||
throw ArgumentError('Could not find enclosing library for ${reference.node}');
|
||||
}
|
||||
|
||||
/// Deferred loading metadata for a single dart2wasm output module.
|
||||
///
|
||||
/// Each [ModuleOutput] will map to a single wasm module emitted by the
|
||||
/// compiler. The separation of modules is guided by the deferred imports
|
||||
/// defined in the source code.
|
||||
///
|
||||
/// A module may contain code at any level of granularity. Code may be grouped
|
||||
/// by library, by class or neither. [containsReference] should be used to
|
||||
/// determine if a module contains a given class/member reference.
|
||||
class ModuleOutput {
|
||||
/// The ID for the module which will be included in the emitted name.
|
||||
final int _id;
|
||||
|
||||
/// The set of libraries contained in this module.
|
||||
final Set<Library> _libraries = {};
|
||||
|
||||
bool get isMain => _id == _mainModuleId;
|
||||
|
||||
/// The name used to import and export this module.
|
||||
String get moduleImportName => 'module$_id';
|
||||
|
||||
/// The name added to the wasm output file for this module.
|
||||
String get moduleName => isMain ? '' : moduleImportName;
|
||||
|
||||
ModuleOutput._(this._id);
|
||||
|
||||
/// Whether or not the provided kernel [Reference] is included in this module.
|
||||
bool containsReference(Reference reference) {
|
||||
final enclosingLibrary = _enclosingLibraryForReference(reference);
|
||||
if (enclosingLibrary == null) return false;
|
||||
return _libraries.contains(enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$moduleImportName($_libraries)';
|
||||
}
|
||||
import 'modules.dart';
|
||||
import 'target.dart';
|
||||
|
||||
/// The root of a deferred import subgraph.
|
||||
///
|
||||
@@ -125,60 +75,21 @@ class _RootSet {
|
||||
/// To support the actual process of loading the deferred wasm modules, we also
|
||||
/// collect a mapping from each import site (i.e. a library and deferred import
|
||||
/// name pair) to the load list needed at that import site.
|
||||
class _LibraryAnalysis {
|
||||
class DeferredLoadingModuleStrategy extends DefaultModuleStrategy {
|
||||
final WasmCompilerOptions options;
|
||||
final Target kernelTarget;
|
||||
final Component component;
|
||||
final WasmTarget kernelTarget;
|
||||
final CoreTypes coreTypes;
|
||||
|
||||
_LibraryAnalysis(
|
||||
this.component, this.options, this.kernelTarget, this.coreTypes);
|
||||
|
||||
ModuleOutputData _buildModuleOutputDataForTestModule() {
|
||||
int moduleIdCounter = _mainModuleId;
|
||||
final mainModule = ModuleOutput._(moduleIdCounter++);
|
||||
final initLibraries =
|
||||
_getTestModeMainLibraries(component, coreTypes, kernelTarget);
|
||||
mainModule._libraries.addAll(initLibraries);
|
||||
final modules = <ModuleOutput>[];
|
||||
final importMap = <String, List<ModuleOutput>>{};
|
||||
|
||||
// Put each library in a separate module.
|
||||
for (final library in component.libraries) {
|
||||
if (initLibraries.contains(library)) continue;
|
||||
final module = ModuleOutput._(moduleIdCounter++);
|
||||
modules.add(module);
|
||||
module._libraries.add(library);
|
||||
final importName = '${library.importUri}';
|
||||
importMap[importName] = [module];
|
||||
}
|
||||
|
||||
final invokeMain =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', '_invokeMain');
|
||||
return ModuleOutputData(
|
||||
[mainModule, ...modules], {invokeMain.enclosingLibrary: importMap});
|
||||
}
|
||||
|
||||
ModuleOutputData _buildModuleOutputDataDisabled() {
|
||||
// If deferred loading is not enabled then put every library in the main
|
||||
// module.
|
||||
final mainModule = ModuleOutput._(_mainModuleId);
|
||||
mainModule._libraries.addAll(component.libraries);
|
||||
return ModuleOutputData([mainModule], const {});
|
||||
}
|
||||
DeferredLoadingModuleStrategy(
|
||||
super.component, this.options, this.kernelTarget, this.coreTypes);
|
||||
|
||||
@override
|
||||
ModuleOutputData buildModuleOutputData() {
|
||||
if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
return _buildModuleOutputDataForTestModule();
|
||||
} else if (!options.translatorOptions.enableDeferredLoading) {
|
||||
return _buildModuleOutputDataDisabled();
|
||||
}
|
||||
|
||||
final (libraryToRootSet, importTargetMap) = _buildLibraryToImports();
|
||||
|
||||
int moduleIdCounter = _mainModuleId;
|
||||
final moduleBuilder = ModuleOutputBuilder();
|
||||
// Dedupe root sets combining equal sets into a single ModuleOutput.
|
||||
final mainModule = ModuleOutput._(moduleIdCounter++);
|
||||
final mainModule = moduleBuilder.buildModule();
|
||||
final Map<_RootSet, ModuleOutput> rootSetToModule = {};
|
||||
final Map<Library, List<ModuleOutput>> rootToModules = {};
|
||||
libraryToRootSet.forEach((targetLibrary, rootSet) {
|
||||
@@ -190,15 +101,15 @@ class _LibraryAnalysis {
|
||||
if (module != null) {
|
||||
// We've already seen a library required by the same roots so added it
|
||||
// to the same module.
|
||||
module._libraries.add(targetLibrary);
|
||||
module.libraries.add(targetLibrary);
|
||||
return;
|
||||
}
|
||||
|
||||
// This library is used by a new set of roots so create a new module for
|
||||
// it. Each root that needs this library should depend on this module.
|
||||
module = rootSetToModule[rootSet] = ModuleOutput._(moduleIdCounter++);
|
||||
module = rootSetToModule[rootSet] = moduleBuilder.buildModule();
|
||||
|
||||
module._libraries.add(targetLibrary);
|
||||
module.libraries.add(targetLibrary);
|
||||
for (final root in rootSet.libraries) {
|
||||
(rootToModules[root] ??= []).add(module);
|
||||
}
|
||||
@@ -244,7 +155,7 @@ class _LibraryAnalysis {
|
||||
// dependencies on these. Also add libraries containing 'wasm:export'
|
||||
// since embedders might need access to these from the main module.
|
||||
for (final lib in component.libraries) {
|
||||
if (_containsExport(coreTypes, lib) || _isRequiredLibrary(lib)) {
|
||||
if (containsWasmExport(coreTypes, lib) || _isRequiredLibrary(lib)) {
|
||||
if (enqueuedEagerLibraries.add(lib)) {
|
||||
eagerWorkStack.add(lib);
|
||||
}
|
||||
@@ -286,132 +197,89 @@ class _LibraryAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
/// Data needed to create deferred modules.
|
||||
class ModuleOutputData {
|
||||
/// All [ModuleOutput]s generated for the program.
|
||||
final List<ModuleOutput> modules;
|
||||
class StressTestModuleStrategy extends ModuleStrategy {
|
||||
final Component component;
|
||||
final CoreTypes coreTypes;
|
||||
final WasmTarget kernelTarget;
|
||||
final ClassHierarchy classHierarchy;
|
||||
|
||||
final Map<Library, Map<String, List<ModuleOutput>>> _importMap;
|
||||
/// We load all 'dart:*' libraries since just doing the deferred load of modules
|
||||
/// requires a significant portion of the SDK libraries.
|
||||
late final Set<Library> _testModeMainLibraries = {
|
||||
...component.libraries.where(
|
||||
(l) => l.importUri.scheme == 'dart' || containsWasmExport(coreTypes, l))
|
||||
};
|
||||
|
||||
ModuleOutputData(this.modules, this._importMap) : assert(modules[0].isMain);
|
||||
StressTestModuleStrategy(
|
||||
this.component, this.coreTypes, this.kernelTarget, this.classHierarchy);
|
||||
|
||||
ModuleOutput get mainModule => modules[0];
|
||||
Iterable<ModuleOutput> get deferredModules => modules.skip(1);
|
||||
|
||||
bool get hasMultipleModules => modules.length > 1;
|
||||
|
||||
/// Mapping from deferred library import to the 'load list' of module names
|
||||
/// needed for that import.
|
||||
/// Augments the `_invokeMain` JS->WASM entry point with test mode setup.
|
||||
///
|
||||
/// If library L is required (either directly or indirectly) by two separate
|
||||
/// imports, then L will be in its own module. That module will be included in
|
||||
/// the load list for both those imports.
|
||||
Map<String, Map<String, List<String>>> generateModuleImportMap() {
|
||||
final result = <String, Map<String, List<String>>>{};
|
||||
_importMap.forEach((lib, importMapping) {
|
||||
final nameMapping = <String, List<String>>{};
|
||||
importMapping.forEach((importName, modules) {
|
||||
nameMapping[importName] =
|
||||
modules.map((o) => o.moduleImportName).toList();
|
||||
});
|
||||
result[lib.importUri.toString()] = nameMapping;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns the module that contains [reference].
|
||||
ModuleOutput moduleForReference(Reference reference) {
|
||||
return modules.firstWhere((e) => e.containsReference(reference));
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates module data for the libraries contained in the provided
|
||||
/// [Component].
|
||||
ModuleOutputData modulesForComponent(Component component,
|
||||
WasmCompilerOptions options, Target kernelTarget, CoreTypes coreTypes) {
|
||||
return _LibraryAnalysis(component, options, kernelTarget, coreTypes)
|
||||
.buildModuleOutputData();
|
||||
}
|
||||
|
||||
Set<Library> _getReachableLibraries(
|
||||
Component component, CoreTypes coreTypes, Target kernelTarget) {
|
||||
final entryPoint = component.mainMethod!.enclosingLibrary;
|
||||
final List<Library> queue = [entryPoint];
|
||||
final Set<Library> reachable = {entryPoint};
|
||||
while (queue.isNotEmpty) {
|
||||
final current = queue.removeLast();
|
||||
for (final dep in current.dependencies) {
|
||||
final importedLib = dep.targetLibrary;
|
||||
if (reachable.add(importedLib)) {
|
||||
queue.add(importedLib);
|
||||
}
|
||||
/// Choosing to augment `_invokeMain` allows us to defer the user-defined
|
||||
/// `main` into a second module ensuring that we always have at least 2
|
||||
/// modules in test mode.
|
||||
@override
|
||||
void prepareComponent() {
|
||||
final initLibraries = _testModeMainLibraries;
|
||||
final loadLibrary =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', 'loadLibrary');
|
||||
final invokeMain =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', '_invokeMain');
|
||||
final loadStatements = <Statement>[];
|
||||
for (final library in getReachableLibraries(
|
||||
component.mainMethod!.enclosingLibrary, coreTypes, kernelTarget)) {
|
||||
if (initLibraries.contains(library)) continue;
|
||||
final loadLibraryCall = StaticInvocation(
|
||||
loadLibrary,
|
||||
Arguments([
|
||||
StringLiteral('${invokeMain.enclosingLibrary.importUri}'),
|
||||
StringLiteral('${library.importUri}')
|
||||
]));
|
||||
loadStatements.add(ExpressionStatement(AwaitExpression(loadLibraryCall)));
|
||||
}
|
||||
}
|
||||
return reachable;
|
||||
}
|
||||
|
||||
bool _hasWasmExportPragma(CoreTypes coreTypes, Member m) =>
|
||||
getPragma(coreTypes, m, 'wasm:export', defaultValue: m.name.text) != null;
|
||||
invokeMain.function.asyncMarker = AsyncMarker.Async;
|
||||
invokeMain.function.emittedValueType = const VoidType();
|
||||
|
||||
bool _containsExport(CoreTypes coreTypes, Library lib) {
|
||||
if (lib.members.any((m) => _hasWasmExportPragma(coreTypes, m))) {
|
||||
return true;
|
||||
}
|
||||
return lib.classes
|
||||
.any((c) => c.members.any((m) => _hasWasmExportPragma(coreTypes, m)));
|
||||
}
|
||||
final oldBody = invokeMain.function.body!;
|
||||
|
||||
/// Augments the `_invokeMain` JS->WASM entry point with test mode setup.
|
||||
///
|
||||
/// Choosing to augment `_invokeMain` allows us to defer the user-defined `main`
|
||||
/// into a second module ensuring that we always have at least 2 modules in test
|
||||
/// mode.
|
||||
void transformComponentForTestMode(Component component,
|
||||
ClassHierarchy classHierarchy, CoreTypes coreTypes, Target kernelTarget) {
|
||||
final initLibraries =
|
||||
_getTestModeMainLibraries(component, coreTypes, kernelTarget);
|
||||
final loadLibrary =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', 'loadLibrary');
|
||||
final invokeMain =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', '_invokeMain');
|
||||
final loadStatements = <Statement>[];
|
||||
for (final library
|
||||
in _getReachableLibraries(component, coreTypes, kernelTarget)) {
|
||||
if (initLibraries.contains(library)) continue;
|
||||
final loadLibraryCall = StaticInvocation(
|
||||
loadLibrary,
|
||||
Arguments([
|
||||
StringLiteral('${invokeMain.enclosingLibrary.importUri}'),
|
||||
StringLiteral('${library.importUri}')
|
||||
]));
|
||||
loadStatements.add(ExpressionStatement(AwaitExpression(loadLibraryCall)));
|
||||
// Add print of 'unittest-suite-wait-for-done' to indicate to test harnesses
|
||||
// that the test contains async work. Any test using test most must therefore
|
||||
// also include a concluding 'unittest-suite-done' message. Usually via calls
|
||||
// to `asyncStart` and `asyncEnd` helpers.
|
||||
final asyncStart = ExpressionStatement(StaticInvocation(
|
||||
coreTypes.printProcedure,
|
||||
Arguments([StringLiteral('unittest-suite-wait-for-done')])));
|
||||
invokeMain.function.body = Block([asyncStart, ...loadStatements, oldBody]);
|
||||
|
||||
// The await transformer runs modularly before this transform so we need to
|
||||
// rerun it on the transformed `_invokeMain` method.
|
||||
await_transformer.transformLibraries(
|
||||
[invokeMain.enclosingLibrary], classHierarchy, coreTypes);
|
||||
}
|
||||
|
||||
invokeMain.function.asyncMarker = AsyncMarker.Async;
|
||||
invokeMain.function.emittedValueType = const VoidType();
|
||||
@override
|
||||
ModuleOutputData buildModuleOutputData() {
|
||||
final moduleBuilder = ModuleOutputBuilder();
|
||||
final mainModule = moduleBuilder.buildModule();
|
||||
final initLibraries = _testModeMainLibraries;
|
||||
mainModule.libraries.addAll(initLibraries);
|
||||
final modules = <ModuleOutput>[];
|
||||
final importMap = <String, List<ModuleOutput>>{};
|
||||
|
||||
final oldBody = invokeMain.function.body!;
|
||||
// Put each library in a separate module.
|
||||
for (final library in component.libraries) {
|
||||
if (initLibraries.contains(library)) continue;
|
||||
final module = moduleBuilder.buildModule();
|
||||
modules.add(module);
|
||||
module.libraries.add(library);
|
||||
final importName = '${library.importUri}';
|
||||
importMap[importName] = [module];
|
||||
}
|
||||
|
||||
// Add print of 'unittest-suite-wait-for-done' to indicate to test harnesses
|
||||
// that the test contains async work. Any test using test most must therefore
|
||||
// also include a concluding 'unittest-suite-done' message. Usually via calls
|
||||
// to `asyncStart` and `asyncEnd` helpers.
|
||||
final asyncStart = ExpressionStatement(StaticInvocation(
|
||||
coreTypes.printProcedure,
|
||||
Arguments([StringLiteral('unittest-suite-wait-for-done')])));
|
||||
invokeMain.function.body = Block([asyncStart, ...loadStatements, oldBody]);
|
||||
|
||||
// The await transformer runs modularly before this transform so we need to
|
||||
// rerun it on the transformed `_invokeMain` method.
|
||||
await_transformer.transformLibraries(
|
||||
[invokeMain.enclosingLibrary], classHierarchy, coreTypes);
|
||||
final invokeMain =
|
||||
coreTypes.index.getTopLevelProcedure('dart:_internal', '_invokeMain');
|
||||
return ModuleOutputData(
|
||||
[mainModule, ...modules], {invokeMain.enclosingLibrary: importMap});
|
||||
}
|
||||
}
|
||||
|
||||
/// We load all 'dart:*' libraries since just doing the deferred load of modules
|
||||
/// requires a significant portion of the SDK libraries.
|
||||
Set<Library> _getTestModeMainLibraries(
|
||||
Component component, CoreTypes coreTypes, Target kernelTarget) =>
|
||||
{
|
||||
...component.libraries.where(
|
||||
(l) => l.importUri.scheme == 'dart' || _containsExport(coreTypes, l))
|
||||
};
|
||||
|
||||
@@ -10,10 +10,17 @@ import 'package:vm/metadata/table_selector.dart';
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
import 'class_info.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'param_info.dart';
|
||||
import 'reference_extensions.dart';
|
||||
import 'translator.dart';
|
||||
|
||||
typedef ModuleSelectorTargets = ({
|
||||
SelectorTargets? checked,
|
||||
SelectorTargets? unchecked,
|
||||
SelectorTargets? normal
|
||||
});
|
||||
|
||||
/// Information for a dispatch table selector.
|
||||
///
|
||||
/// A selector encapsulates information to generate code that selects the right
|
||||
@@ -30,6 +37,10 @@ class SelectorInfo {
|
||||
/// Unique ID of the selector.
|
||||
final int id;
|
||||
|
||||
// The ID of the selector in the main module. Only populated for dynamic
|
||||
// modules.
|
||||
final Map<Member, int> _mainModuleIds = {};
|
||||
|
||||
/// Number of use sites of the selector.
|
||||
final int callCount;
|
||||
|
||||
@@ -45,14 +56,14 @@ class SelectorInfo {
|
||||
/// performs type checks on the passed arguments.
|
||||
bool useMultipleEntryPoints = false;
|
||||
|
||||
bool isDynamicModuleOverrideable = false;
|
||||
bool isDynamicModuleCallable = false;
|
||||
|
||||
/// Wasm function type for the selector.
|
||||
///
|
||||
/// This should be read after all targets have been added to the selector.
|
||||
late final w.FunctionType signature = _computeSignature();
|
||||
|
||||
/// Number of concrete classes that provide this selector.
|
||||
late final int concreteClasses;
|
||||
|
||||
/// The selector's member's name.
|
||||
final String name;
|
||||
|
||||
@@ -61,24 +72,34 @@ class SelectorInfo {
|
||||
/// `noSuchMethod` overrides to the dispatch table.
|
||||
final bool isNoSuchMethod;
|
||||
|
||||
late final SelectorTargets? _normal;
|
||||
late final SelectorTargets? _checked;
|
||||
late final SelectorTargets? _unchecked;
|
||||
late final ModuleSelectorTargets _mainModuleTargets;
|
||||
late final ModuleSelectorTargets _dynamicModuleTargets;
|
||||
|
||||
SelectorTargets targets({required bool unchecked}) {
|
||||
SelectorTargets targets(
|
||||
{required bool unchecked, required bool dynamicModule}) {
|
||||
final selectorTargets =
|
||||
dynamicModule ? _dynamicModuleTargets : _mainModuleTargets;
|
||||
if (useMultipleEntryPoints) {
|
||||
assert(_checked!.targetRanges.length == _unchecked!.targetRanges.length);
|
||||
assert(_checked!.staticDispatchRanges.length ==
|
||||
_unchecked!.staticDispatchRanges.length);
|
||||
return unchecked ? _unchecked! : _checked!;
|
||||
assert(selectorTargets.checked!.targetRanges.length ==
|
||||
selectorTargets.unchecked!.targetRanges.length);
|
||||
return (unchecked ? selectorTargets.unchecked : selectorTargets.checked)!;
|
||||
}
|
||||
assert(_checked == null && _unchecked == null);
|
||||
return _normal!;
|
||||
assert(
|
||||
selectorTargets.checked == null && selectorTargets.unchecked == null);
|
||||
return selectorTargets.normal!;
|
||||
}
|
||||
|
||||
SelectorInfo._(
|
||||
this.translator, this.id, this.name, this.callCount, this.paramInfo,
|
||||
{required this.isSetter, required this.isNoSuchMethod});
|
||||
this.translator,
|
||||
this.id,
|
||||
this.name,
|
||||
this.callCount,
|
||||
this.paramInfo, {
|
||||
required this.isSetter,
|
||||
required this.isNoSuchMethod,
|
||||
});
|
||||
|
||||
int mainModuleIdForTarget(Member member) => _mainModuleIds[member]!;
|
||||
|
||||
String entryPointName(bool unchecked) {
|
||||
if (!useMultipleEntryPoints) return name;
|
||||
@@ -99,7 +120,13 @@ class SelectorInfo {
|
||||
List.generate(1 + paramInfo.paramCount, (_) => {});
|
||||
List<Set<w.ValueType>> outputSets = List.generate(returnCount, (_) => {});
|
||||
List<bool> ensureBoxed = List.filled(1 + paramInfo.paramCount, false);
|
||||
for (final (range: _, :target) in targets(unchecked: false).targetRanges) {
|
||||
Iterable<({Reference target, Range range})> targetRanges =
|
||||
targets(unchecked: false, dynamicModule: false).targetRanges;
|
||||
if (translator.isDynamicModule) {
|
||||
targetRanges = targetRanges.followedBy(
|
||||
targets(unchecked: false, dynamicModule: true).targetRanges);
|
||||
}
|
||||
for (final (range: _, :target) in targetRanges) {
|
||||
Member member = target.asMember;
|
||||
DartType receiver =
|
||||
InterfaceType(member.enclosingClass!, Nullability.nonNullable);
|
||||
@@ -219,9 +246,22 @@ class SelectorInfo {
|
||||
return w.RefType.def(heapTypes.single, nullable: nullable);
|
||||
}
|
||||
|
||||
late final Set<Reference> targetSet = useMultipleEntryPoints
|
||||
? {..._unchecked!._targetSet, ..._checked!._targetSet}
|
||||
: _normal!._targetSet;
|
||||
late final Set<Reference> _targetSet = useMultipleEntryPoints
|
||||
? {
|
||||
..._mainModuleTargets.checked!._targetSet,
|
||||
..._mainModuleTargets.unchecked!._targetSet,
|
||||
if (translator.isDynamicModule) ...{
|
||||
..._dynamicModuleTargets.checked!._targetSet,
|
||||
..._dynamicModuleTargets.unchecked!._targetSet,
|
||||
}
|
||||
}
|
||||
: {
|
||||
..._mainModuleTargets.normal!._targetSet,
|
||||
if (translator.isDynamicModule)
|
||||
..._dynamicModuleTargets.normal!._targetSet
|
||||
};
|
||||
|
||||
bool containsTarget(Reference target) => _targetSet.contains(target);
|
||||
}
|
||||
|
||||
class SelectorTargets {
|
||||
@@ -245,7 +285,7 @@ class SelectorTargets {
|
||||
///
|
||||
/// For a class in [targetRanges], `class ID + offset` gives the offset of the
|
||||
/// class member for this selector.
|
||||
late final int offset;
|
||||
int? offset;
|
||||
|
||||
SelectorTargets(this.targetRanges, this.staticDispatchRanges);
|
||||
|
||||
@@ -275,11 +315,15 @@ class DispatchTable {
|
||||
/// Contents of [_definedWasmTable]. For a selector with ID S and a target
|
||||
/// class of the selector with ID C, `table[S + C]` gives the reference to the
|
||||
/// class member for the selector.
|
||||
late final List<Reference?> _table;
|
||||
late final List<Reference?> table;
|
||||
List<Reference?>? _dynamicModuleTable;
|
||||
|
||||
late final w.TableBuilder _definedWasmTable;
|
||||
final WasmTableImporter _importedWasmTables;
|
||||
|
||||
w.TableBuilder? _dynamicModuleDefinedWasmTable;
|
||||
w.Table get dynamicModuleDefinedWasmTable => _dynamicModuleDefinedWasmTable!;
|
||||
|
||||
/// The Wasm table for the dispatch table.
|
||||
w.Table getWasmTable(w.ModuleBuilder module) =>
|
||||
_importedWasmTables.get(_definedWasmTable, module);
|
||||
@@ -323,6 +367,9 @@ class DispatchTable {
|
||||
metadata.methodOrSetterCalledDynamically ||
|
||||
member.name.text == "call");
|
||||
|
||||
final isDynamicModuleOverrideable =
|
||||
member.isDynamicModuleOverrideable(translator.coreTypes);
|
||||
|
||||
final selector = _selectorInfo.putIfAbsent(
|
||||
selectorId,
|
||||
() => SelectorInfo._(translator, selectorId, member.name.text,
|
||||
@@ -331,10 +378,15 @@ class DispatchTable {
|
||||
isNoSuchMethod: member == translator.objectNoSuchMethod));
|
||||
assert(selector.isSetter == isSetter);
|
||||
final useMultipleEntryPoints = !member.isAbstract &&
|
||||
!member.isExternal &&
|
||||
!target.isGetter &&
|
||||
!target.isTearOffReference &&
|
||||
translator.needToCheckTypesFor(member);
|
||||
selector.useMultipleEntryPoints |= useMultipleEntryPoints;
|
||||
selector.isDynamicModuleOverrideable |= isDynamicModuleOverrideable;
|
||||
selector.isDynamicModuleCallable |=
|
||||
member.isDynamicModuleCallable(translator.coreTypes);
|
||||
|
||||
selector.paramInfo.merge(paramInfo);
|
||||
if (calledDynamically) {
|
||||
if (isGetter) {
|
||||
@@ -345,6 +397,13 @@ class DispatchTable {
|
||||
(_dynamicMethods[member.name.text] ??= {}).add(selector);
|
||||
}
|
||||
}
|
||||
final mainModuleIds = translator.dynamicModuleInfo?.selectorIds?[member];
|
||||
if (mainModuleIds != null) {
|
||||
selector._mainModuleIds[member] =
|
||||
isGetter ? mainModuleIds.$1 : mainModuleIds.$2;
|
||||
} else {
|
||||
selector._mainModuleIds[member] = selectorId;
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
@@ -425,10 +484,21 @@ class DispatchTable {
|
||||
} else if (member is Procedure) {
|
||||
final target = member.reference;
|
||||
addMember(target, staticDispatch);
|
||||
final procedureMetadata =
|
||||
translator.procedureAttributeMetadata[member]!;
|
||||
// `hasTearOffUses` can be true for operators as well, even though
|
||||
// it's not possible to tear-off an operator. (no syntax for it)
|
||||
if (member.kind == ProcedureKind.Method &&
|
||||
translator.procedureAttributeMetadata[member]!.hasTearOffUses) {
|
||||
(procedureMetadata.hasTearOffUses ||
|
||||
// Only concrete members have 'hasTearOffUse' but for dynamic
|
||||
// module compilations, there may be any concrete
|
||||
// implementations yet. We check if the member is dynamically
|
||||
// overrideable at the tearoff sites as they might not be
|
||||
// tree-shaken. We still need a selector at the tearoff site.
|
||||
(translator.dynamicModuleSupportEnabled &&
|
||||
_selectorMetadata[
|
||||
procedureMetadata.methodOrSetterSelectorId]
|
||||
.tornOff))) {
|
||||
addMember(member.tearOffReference, staticDispatch);
|
||||
}
|
||||
}
|
||||
@@ -436,179 +506,238 @@ class DispatchTable {
|
||||
selectorsInClass[cls] = selectors;
|
||||
}
|
||||
|
||||
final selectorTargets = <SelectorInfo, Map<int, Reference>>{};
|
||||
final maxConcreteClassId = translator.classIdNumbering.maxConcreteClassId;
|
||||
for (int classId = 0; classId <= maxConcreteClassId; ++classId) {
|
||||
final cls = translator.classes[classId].cls;
|
||||
if (cls != null) {
|
||||
selectorsInClass[cls]!.forEach((selectorInfo, target) {
|
||||
if (!target.asMember.isAbstract) {
|
||||
selectorTargets.putIfAbsent(selectorInfo, () => {})[classId] =
|
||||
target;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
selectorTargets
|
||||
.forEach((SelectorInfo selector, Map<int, Reference> targets) {
|
||||
selector.concreteClasses = targets.length;
|
||||
|
||||
final List<({Range range, Reference target})> ranges = targets.entries
|
||||
.map((entry) =>
|
||||
(range: Range(entry.key, entry.key), target: entry.value))
|
||||
.toList()
|
||||
..sort((a, b) => a.range.start.compareTo(b.range.start));
|
||||
assert(ranges.isNotEmpty);
|
||||
int writeIndex = 0;
|
||||
for (int readIndex = 1; readIndex < ranges.length; ++readIndex) {
|
||||
final current = ranges[writeIndex];
|
||||
final next = ranges[readIndex];
|
||||
assert(next.range.length == 1);
|
||||
if ((current.range.end + 1) == next.range.start &&
|
||||
identical(current.target, next.target)) {
|
||||
ranges[writeIndex] = (
|
||||
range: Range(current.range.start, next.range.end),
|
||||
target: current.target
|
||||
);
|
||||
} else {
|
||||
ranges[++writeIndex] = next;
|
||||
List<Reference?> processTargets(int start, int end, bool isDynamicModule) {
|
||||
final selectorTargets = <SelectorInfo, Map<int, Reference>>{};
|
||||
for (int classId = start; classId <= end; ++classId) {
|
||||
final cls = translator.classes[classId].cls;
|
||||
if (cls != null) {
|
||||
selectorsInClass[cls]!.forEach((selectorInfo, target) {
|
||||
if (!target.asMember.isAbstract) {
|
||||
selectorTargets.putIfAbsent(selectorInfo, () => {})[classId] =
|
||||
target;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ranges.length = writeIndex + 1;
|
||||
|
||||
final staticDispatchRanges = (translator
|
||||
.options.polymorphicSpecialization ||
|
||||
ranges.length == 1)
|
||||
? ranges
|
||||
: ranges
|
||||
.where((range) => staticDispatchPragmas.contains(range.target))
|
||||
.toList();
|
||||
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
({Range range, Reference target}) getChecked(
|
||||
({Range range, Reference target}) targetRange,
|
||||
bool unchecked,
|
||||
) =>
|
||||
(
|
||||
range: targetRange.range,
|
||||
target: translator.getFunctionEntry(targetRange.target,
|
||||
uncheckedEntry: unchecked)
|
||||
selectorTargets
|
||||
.forEach((SelectorInfo selector, Map<int, Reference> targets) {
|
||||
final List<({Range range, Reference target})> ranges = targets.entries
|
||||
.map((entry) =>
|
||||
(range: Range(entry.key, entry.key), target: entry.value))
|
||||
.toList()
|
||||
..sort((a, b) => a.range.start.compareTo(b.range.start));
|
||||
assert(ranges.isNotEmpty);
|
||||
int writeIndex = 0;
|
||||
for (int readIndex = 1; readIndex < ranges.length; ++readIndex) {
|
||||
final current = ranges[writeIndex];
|
||||
final next = ranges[readIndex];
|
||||
assert(next.range.length == 1);
|
||||
if ((current.range.end + 1) == next.range.start &&
|
||||
identical(current.target, next.target)) {
|
||||
ranges[writeIndex] = (
|
||||
range: Range(current.range.start, next.range.end),
|
||||
target: current.target
|
||||
);
|
||||
|
||||
selector._normal = null;
|
||||
selector._checked = SelectorTargets(
|
||||
ranges.map((r) => getChecked(r, false)).toList(),
|
||||
staticDispatchRanges.map((r) => getChecked(r, false)).toList(),
|
||||
);
|
||||
selector._unchecked = SelectorTargets(
|
||||
ranges.map((r) => getChecked(r, true)).toList(),
|
||||
staticDispatchRanges.map((r) => getChecked(r, true)).toList(),
|
||||
);
|
||||
} else {
|
||||
selector._normal = SelectorTargets(ranges, staticDispatchRanges);
|
||||
selector._checked = null;
|
||||
selector._unchecked = null;
|
||||
}
|
||||
});
|
||||
|
||||
_selectorInfo.forEach((_, selector) {
|
||||
if (!selectorTargets.containsKey(selector)) {
|
||||
// There are no concrete implementations for the given [selector].
|
||||
// But there may be an abstract interface target which is targed by a
|
||||
// call. In this case the call should be unreachable.
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
selector._normal = null;
|
||||
selector._checked = SelectorTargets([], []);
|
||||
selector._unchecked = SelectorTargets([], []);
|
||||
} else {
|
||||
selector._normal = SelectorTargets([], []);
|
||||
selector._checked = null;
|
||||
selector._unchecked = null;
|
||||
} else {
|
||||
ranges[++writeIndex] = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ranges.length = writeIndex + 1;
|
||||
|
||||
// Assign selector offsets
|
||||
final staticDispatchRanges = selector.isDynamicModuleOverrideable
|
||||
? const <({Range range, Reference target})>[]
|
||||
: (translator.options.polymorphicSpecialization ||
|
||||
ranges.length == 1)
|
||||
? ranges
|
||||
: ranges
|
||||
.where(
|
||||
(range) => staticDispatchPragmas.contains(range.target))
|
||||
.toList();
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
({Range range, Reference target}) getChecked(
|
||||
({Range range, Reference target}) targetRange,
|
||||
bool unchecked,
|
||||
) =>
|
||||
(
|
||||
range: targetRange.range,
|
||||
target: translator.getFunctionEntry(targetRange.target,
|
||||
uncheckedEntry: unchecked)
|
||||
);
|
||||
final checkedTargets = SelectorTargets(
|
||||
ranges.map((r) => getChecked(r, false)).toList(),
|
||||
staticDispatchRanges.map((r) => getChecked(r, false)).toList(),
|
||||
);
|
||||
final uncheckedTargets = SelectorTargets(
|
||||
ranges.map((r) => getChecked(r, true)).toList(),
|
||||
staticDispatchRanges.map((r) => getChecked(r, true)).toList(),
|
||||
);
|
||||
final targets = (
|
||||
normal: null,
|
||||
checked: checkedTargets,
|
||||
unchecked: uncheckedTargets
|
||||
);
|
||||
if (isDynamicModule) {
|
||||
selector._dynamicModuleTargets = targets;
|
||||
} else {
|
||||
selector._mainModuleTargets = targets;
|
||||
}
|
||||
} else {
|
||||
final normalTargets = SelectorTargets(ranges, staticDispatchRanges);
|
||||
final targets =
|
||||
(normal: normalTargets, checked: null, unchecked: null);
|
||||
if (isDynamicModule) {
|
||||
selector._dynamicModuleTargets = targets;
|
||||
} else {
|
||||
selector._mainModuleTargets = targets;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Assign selector offsets
|
||||
|
||||
bool isUsedViaDispatchTableCall(SelectorInfo selector) {
|
||||
// Special case for `objectNoSuchMethod`: we introduce instance
|
||||
// invocations of `objectNoSuchMethod` in dynamic calls, so keep it alive
|
||||
// even if there was no references to it from the Dart code.
|
||||
if (selector.isNoSuchMethod) {
|
||||
return true;
|
||||
}
|
||||
if (selector.isDynamicModuleCallable) return true;
|
||||
|
||||
if (selector.callCount == 0) {
|
||||
return false;
|
||||
}
|
||||
if (selector.isDynamicModuleOverrideable) return true;
|
||||
|
||||
final targets =
|
||||
selector.targets(unchecked: false, dynamicModule: isDynamicModule);
|
||||
|
||||
if (targets.targetRanges.length <= 1) return false;
|
||||
if (!isDynamicModule &&
|
||||
targets.staticDispatchRanges.length ==
|
||||
targets.targetRanges.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isUsedViaDispatchTableCall(SelectorInfo selector) {
|
||||
if (selector.isNoSuchMethod) {
|
||||
return true;
|
||||
}
|
||||
if (selector.callCount == 0) return false;
|
||||
|
||||
final targets = selector.targets(unchecked: false);
|
||||
final List<SelectorInfo> selectors =
|
||||
selectorTargets.keys.where(isUsedViaDispatchTableCall).toList();
|
||||
|
||||
if (targets.targetRanges.length <= 1) return false;
|
||||
if (targets.staticDispatchRanges.length == targets.targetRanges.length) {
|
||||
return false;
|
||||
// Sort the selectors based on number of targets and number of use sites.
|
||||
// This is a heuristic to keep the table small.
|
||||
//
|
||||
// Place selectors with more targets first as they are less likely to fit
|
||||
// into the gaps left by selectors placed earlier.
|
||||
//
|
||||
// Among the selectors with approximately same number of targets, place
|
||||
// more used ones first, as the smaller selector offset will have a smaller
|
||||
// instruction encoding.
|
||||
int selectorSortWeight(SelectorInfo selector) =>
|
||||
selectorTargets[selector]!.length * 10 + selector.callCount;
|
||||
|
||||
selectors.sort((a, b) => selectorSortWeight(b) - selectorSortWeight(a));
|
||||
|
||||
final rows = <Row<Reference>>[];
|
||||
for (final selector in selectors) {
|
||||
Row<Reference> buildRow(
|
||||
List<({Range range, Reference target})> targetRanges) {
|
||||
final rowValues = <({int index, Reference value})>[];
|
||||
for (final (:range, :target) in targetRanges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
final adjustedClassId = classId - start;
|
||||
rowValues.add((index: adjustedClassId, value: target));
|
||||
}
|
||||
}
|
||||
rowValues.sort((a, b) => a.index.compareTo(b.index));
|
||||
return Row(rowValues);
|
||||
}
|
||||
|
||||
final selectorTargets = isDynamicModule
|
||||
? selector._dynamicModuleTargets
|
||||
: selector._mainModuleTargets;
|
||||
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
rows.add(buildRow((selectorTargets.checked!.targetRanges)));
|
||||
rows.add(buildRow((selectorTargets.unchecked!.targetRanges)));
|
||||
} else {
|
||||
rows.add(buildRow((selectorTargets.normal!.targetRanges)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final List<SelectorInfo> selectors =
|
||||
selectorTargets.keys.where(isUsedViaDispatchTableCall).toList();
|
||||
final table = buildRowDisplacementTable<Reference>(rows);
|
||||
|
||||
// Sort the selectors based on number of targets and number of use sites.
|
||||
// This is a heuristic to keep the table small.
|
||||
//
|
||||
// Place selectors with more targets first as they are less likely to fit
|
||||
// into the gaps left by selectors placed earlier.
|
||||
//
|
||||
// Among the selectors with approximately same number of targets, place
|
||||
// more used ones first, as the smaller selector offset will have a smaller
|
||||
// instruction encoding.
|
||||
int selectorSortWeight(SelectorInfo selector) =>
|
||||
selector.concreteClasses * 10 + selector.callCount;
|
||||
int rowIndex = 0;
|
||||
for (final selector in selectors) {
|
||||
final selectorTargets = isDynamicModule
|
||||
? selector._dynamicModuleTargets
|
||||
: selector._mainModuleTargets;
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
selectorTargets.checked!.offset = rows[rowIndex++].offset;
|
||||
selectorTargets.unchecked!.offset = rows[rowIndex++].offset;
|
||||
} else {
|
||||
selectorTargets.normal!.offset = rows[rowIndex++].offset;
|
||||
}
|
||||
}
|
||||
|
||||
selectors.sort((a, b) => selectorSortWeight(b) - selectorSortWeight(a));
|
||||
|
||||
final rows = <Row<Reference>>[];
|
||||
for (final selector in selectors) {
|
||||
Row<Reference> buildRow(
|
||||
List<({Range range, Reference target})> targetRanges) {
|
||||
final rowValues = <({int index, Reference value})>[];
|
||||
for (final (:range, :target) in targetRanges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
rowValues.add((index: classId, value: target));
|
||||
_selectorInfo.forEach((_, selector) {
|
||||
if (!selectorTargets.containsKey(selector)) {
|
||||
// There are no concrete implementations for the given [selector].
|
||||
// But there may be an abstract interface target which is targed by a
|
||||
// call. In this case the call should be unreachable.
|
||||
final targets = selector.useMultipleEntryPoints
|
||||
? (
|
||||
normal: null,
|
||||
checked: SelectorTargets(const [], const []),
|
||||
unchecked: SelectorTargets(const [], const [])
|
||||
)
|
||||
: (
|
||||
normal: SelectorTargets(const [], const []),
|
||||
checked: null,
|
||||
unchecked: null,
|
||||
);
|
||||
if (isDynamicModule) {
|
||||
selector._dynamicModuleTargets = targets;
|
||||
} else {
|
||||
selector._mainModuleTargets = targets;
|
||||
}
|
||||
}
|
||||
rowValues.sort((a, b) => a.index.compareTo(b.index));
|
||||
return Row(rowValues);
|
||||
}
|
||||
});
|
||||
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
rows.add(buildRow(selector._checked!.targetRanges));
|
||||
rows.add(buildRow(selector._unchecked!.targetRanges));
|
||||
} else {
|
||||
rows.add(buildRow(selector._normal!.targetRanges));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
_table = buildRowDisplacementTable<Reference>(rows);
|
||||
|
||||
int rowIndex = 0;
|
||||
for (final selector in selectors) {
|
||||
if (selector.useMultipleEntryPoints) {
|
||||
selector._checked!.offset = rows[rowIndex++].offset;
|
||||
selector._unchecked!.offset = rows[rowIndex++].offset;
|
||||
} else {
|
||||
selector._normal!.offset = rows[rowIndex++].offset;
|
||||
}
|
||||
}
|
||||
table = processTargets(
|
||||
0, translator.classIdNumbering.maxConcreteClassId, false);
|
||||
|
||||
_definedWasmTable =
|
||||
translator.mainModule.tables.define(_functionType, _table.length);
|
||||
for (final module in translator.modules) {
|
||||
// Ensure the dispatch table is imported into every module as the first
|
||||
// table.
|
||||
getWasmTable(module);
|
||||
translator.mainModule.tables.define(_functionType, table.length);
|
||||
if (!translator.dynamicModuleSupportEnabled) {
|
||||
// Dynamic modules don't need direct access to the main module's table.
|
||||
// Accesses are routed through global dispatch function refs.
|
||||
for (final module in translator.modules) {
|
||||
// Ensure the dispatch table is imported into every module as the first
|
||||
// table.
|
||||
getWasmTable(module);
|
||||
}
|
||||
}
|
||||
|
||||
if (translator.isDynamicModule) {
|
||||
_dynamicModuleTable = processTargets(
|
||||
translator.classIdNumbering.firstDynamicModuleClassId,
|
||||
translator.classIdNumbering.maxDynamicModuleConcreteClassId!,
|
||||
true);
|
||||
|
||||
_dynamicModuleDefinedWasmTable = translator.dynamicModule.tables
|
||||
.define(_functionType, _dynamicModuleTable!.length);
|
||||
}
|
||||
}
|
||||
|
||||
void output() {
|
||||
for (int i = 0; i < _table.length; i++) {
|
||||
Reference? target = _table[i];
|
||||
for (int i = 0; i < table.length; i++) {
|
||||
Reference? target = table[i];
|
||||
if (target != null) {
|
||||
w.BaseFunction? fun = translator.functions.getExistingFunction(target);
|
||||
// Any call to the dispatch table is guaranteed to hit a target.
|
||||
@@ -621,7 +750,7 @@ class DispatchTable {
|
||||
// module must've been loaded to call the constructor.
|
||||
if (fun != null) {
|
||||
final targetModule = translator.moduleForReference(target);
|
||||
if (translator.isMainModule(targetModule)) {
|
||||
if (targetModule == _definedWasmTable.enclosingModule) {
|
||||
_definedWasmTable.setElement(i, fun);
|
||||
} else {
|
||||
// This will generate the imported table if it doesn't already
|
||||
@@ -632,6 +761,29 @@ class DispatchTable {
|
||||
}
|
||||
}
|
||||
}
|
||||
final dynamicModuleDefinedWasmTable = _dynamicModuleDefinedWasmTable;
|
||||
if (dynamicModuleDefinedWasmTable != null) {
|
||||
final dynamicModuleTable = _dynamicModuleTable!;
|
||||
final targetModule = dynamicModuleDefinedWasmTable.enclosingModule;
|
||||
for (int i = 0; i < dynamicModuleTable.length; i++) {
|
||||
Reference? target = dynamicModuleTable[i];
|
||||
if (target != null) {
|
||||
w.BaseFunction? fun =
|
||||
translator.functions.getExistingFunction(target);
|
||||
if (fun != null) {
|
||||
if (fun.enclosingModule != targetModule) {
|
||||
if (target.asMember
|
||||
.isDynamicModuleCallable(translator.coreTypes)) {
|
||||
fun = translator.functions.importFunctionToDynamicModule(fun);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
dynamicModuleDefinedWasmTable.setElement(i, fun);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,11 @@ class Forwarder {
|
||||
void _generateGetterCode(Translator translator) {
|
||||
final selectors =
|
||||
translator.dispatchTable.dynamicGetterSelectors(memberName);
|
||||
final isDynamicModule = translator.isDynamicModule &&
|
||||
function.enclosingModule == translator.dynamicModule;
|
||||
final ranges = selectors
|
||||
.expand((selector) => selector
|
||||
.targets(unchecked: false)
|
||||
.targets(unchecked: false, dynamicModule: isDynamicModule)
|
||||
.targetRanges
|
||||
.map((r) => (range: r.range, value: r.target)))
|
||||
.toList();
|
||||
@@ -146,9 +148,11 @@ class Forwarder {
|
||||
void _generateSetterCode(Translator translator) {
|
||||
final selectors =
|
||||
translator.dispatchTable.dynamicSetterSelectors(memberName);
|
||||
final isDynamicModule = translator.isDynamicModule &&
|
||||
function.enclosingModule == translator.dynamicModule;
|
||||
final ranges = selectors
|
||||
.expand((selector) => selector
|
||||
.targets(unchecked: false)
|
||||
.targets(unchecked: false, dynamicModule: isDynamicModule)
|
||||
.targetRanges
|
||||
.map((r) => (range: r.range, value: r.target)))
|
||||
.toList();
|
||||
@@ -201,8 +205,11 @@ class Forwarder {
|
||||
for (final selector in methodSelectors) {
|
||||
// Accumulates all class ID ranges that have the same target.
|
||||
final Map<Reference, List<Range>> targets = {};
|
||||
for (final (:range, :target)
|
||||
in selector.targets(unchecked: false).targetRanges) {
|
||||
final isDynamicModule = translator.isDynamicModule &&
|
||||
function.enclosingModule == translator.dynamicModule;
|
||||
for (final (:range, :target) in selector
|
||||
.targets(unchecked: false, dynamicModule: isDynamicModule)
|
||||
.targetRanges) {
|
||||
targets.putIfAbsent(target, () => []).add(range);
|
||||
}
|
||||
|
||||
@@ -481,9 +488,12 @@ class Forwarder {
|
||||
final getterSelectors =
|
||||
translator.dispatchTable.dynamicGetterSelectors(memberName);
|
||||
final getterValueLocal = b.addLocal(translator.topInfo.nullableType);
|
||||
final isDynamicModule = translator.isDynamicModule &&
|
||||
function.enclosingModule == translator.dynamicModule;
|
||||
for (final selector in getterSelectors) {
|
||||
for (final (:range, :target)
|
||||
in selector.targets(unchecked: false).targetRanges) {
|
||||
for (final (:range, :target) in selector
|
||||
.targets(unchecked: false, dynamicModule: isDynamicModule)
|
||||
.targetRanges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
final targetMember = target.asMember;
|
||||
// This loop checks getters and fields. Methods are considered in the
|
||||
@@ -528,7 +538,8 @@ class Forwarder {
|
||||
|
||||
// Invoke "call" if the value is not a closure
|
||||
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
||||
b.i32_const(translator.closureInfo.classId);
|
||||
b.i32_const(
|
||||
(translator.closureInfo.classId as AbsoluteClassId).value);
|
||||
b.i32_ne();
|
||||
b.if_();
|
||||
// Value is not a closure
|
||||
@@ -780,7 +791,7 @@ void generateNoSuchMethodCall(
|
||||
// Get class id for virtual call
|
||||
pushReceiver();
|
||||
translator.callDispatchTable(b, noSuchMethodSelector,
|
||||
useUncheckedEntry: false);
|
||||
interfaceTarget: translator.objectNoSuchMethod, useUncheckedEntry: false);
|
||||
}
|
||||
|
||||
class ClassIdRange {
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
// 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 'package:kernel/ast.dart';
|
||||
|
||||
import 'compiler_options.dart';
|
||||
import 'serialization.dart';
|
||||
import 'translator.dart';
|
||||
|
||||
/// 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) {
|
||||
throw UnsupportedError('');
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToBinary(int globalId, Node node, BinarySink sink) {}
|
||||
}
|
||||
|
||||
/// 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 dynamic
|
||||
/// module compilations.
|
||||
class DynamicModuleMetadata {
|
||||
/// Global kernel class ID to dart2wasm class hierarchy class ID.
|
||||
final Map<int, int> classIds;
|
||||
|
||||
/// Global kernel member ID to getter and setter/method selector ID.
|
||||
final Map<int, (int, int)> selectorIds;
|
||||
|
||||
/// Global kernel class IDs in class hierarchy dfs order.
|
||||
final List<int> dfsOrderClassIds;
|
||||
|
||||
/// References for all targets callable from the main module represented as
|
||||
/// member global ID and reference type.
|
||||
final List<(int, int)> callableReferences;
|
||||
|
||||
/// Key names of updateable functions defined in the main module.
|
||||
final Map<String, int> updateableFunctionsInMain;
|
||||
|
||||
/// Saved flags from the main module to verify that settings have not changed
|
||||
/// between main module invocation and dynamic module invocation.
|
||||
final TranslatorOptions mainModuleTranslatorOptions;
|
||||
final Map<String, String> mainModuleEnvironment;
|
||||
|
||||
DynamicModuleMetadata(
|
||||
this.classIds,
|
||||
this.selectorIds,
|
||||
this.dfsOrderClassIds,
|
||||
this.callableReferences,
|
||||
this.updateableFunctionsInMain,
|
||||
this.mainModuleTranslatorOptions,
|
||||
this.mainModuleEnvironment);
|
||||
|
||||
void serialize(BinaryDataSink sink) {
|
||||
sink.writeInt(classIds.length);
|
||||
classIds.forEach((globalClassId, classId) {
|
||||
sink.writeInt(globalClassId);
|
||||
sink.writeClassId(classId);
|
||||
});
|
||||
sink.writeInt(selectorIds.length);
|
||||
selectorIds.forEach((globalMemberId, selectorIds) {
|
||||
sink.writeInt(globalMemberId);
|
||||
sink.writeInt(selectorIds.$1);
|
||||
sink.writeInt(selectorIds.$2);
|
||||
});
|
||||
sink.writeInt(dfsOrderClassIds.length);
|
||||
for (final classId in dfsOrderClassIds) {
|
||||
sink.writeClassId(classId);
|
||||
}
|
||||
sink.writeInt(callableReferences.length);
|
||||
for (final callableMemberId in callableReferences) {
|
||||
sink.writeInt(callableMemberId.$1);
|
||||
sink.writeInt(callableMemberId.$2);
|
||||
}
|
||||
sink.writeInt(updateableFunctionsInMain.length);
|
||||
updateableFunctionsInMain.forEach((stringKey, key) {
|
||||
sink.writeString(stringKey);
|
||||
sink.writeInt(key);
|
||||
});
|
||||
|
||||
mainModuleTranslatorOptions.serialize(sink);
|
||||
sink.writeInt(mainModuleEnvironment.length);
|
||||
mainModuleEnvironment.forEach((k, v) {
|
||||
sink.writeString(k);
|
||||
sink.writeString(v);
|
||||
});
|
||||
}
|
||||
|
||||
static DynamicModuleMetadata deserialize(BinaryDataSource source) {
|
||||
final classIdMappingLength = source.readInt();
|
||||
final classIds = <int, int>{};
|
||||
for (int i = 0; i < classIdMappingLength; i++) {
|
||||
final globalClassId = source.readInt();
|
||||
final classId = source.readClassId();
|
||||
classIds[globalClassId] = classId;
|
||||
}
|
||||
|
||||
final selectorIdMappingLength = source.readInt();
|
||||
final selectorIds = <int, (int, int)>{};
|
||||
for (int i = 0; i < selectorIdMappingLength; i++) {
|
||||
final globalMemberId = source.readInt();
|
||||
final getterSelectorId = source.readInt();
|
||||
final setterOrMethodSelectorId = source.readInt();
|
||||
selectorIds[globalMemberId] =
|
||||
(getterSelectorId, setterOrMethodSelectorId);
|
||||
}
|
||||
final dfsOrderClassIdsLength = source.readInt();
|
||||
final dfsOrderClassIds = <int>[];
|
||||
for (int i = 0; i < dfsOrderClassIdsLength; i++) {
|
||||
dfsOrderClassIds.add(source.readClassId());
|
||||
}
|
||||
final callableMemberIdsLength = source.readInt();
|
||||
final callableMemberIds = <(int, int)>[];
|
||||
for (int i = 0; i < callableMemberIdsLength; i++) {
|
||||
callableMemberIds.add((source.readInt(), source.readInt()));
|
||||
}
|
||||
final updateableFunctionsInMainLength = source.readInt();
|
||||
final updateableFunctionsInMain = <String, int>{};
|
||||
for (int i = 0; i < updateableFunctionsInMainLength; i++) {
|
||||
final stringKey = source.readString();
|
||||
final key = source.readInt();
|
||||
updateableFunctionsInMain[stringKey] = key;
|
||||
}
|
||||
final mainModuleTranslatorOptions = TranslatorOptions.deserialize(source);
|
||||
final mainModuleEnvironmentLength = source.readInt();
|
||||
final mainModuleEnvironment = <String, String>{};
|
||||
for (int i = 0; i < mainModuleEnvironmentLength; i++) {
|
||||
final key = source.readString();
|
||||
final value = source.readString();
|
||||
mainModuleEnvironment[key] = value;
|
||||
}
|
||||
|
||||
return DynamicModuleMetadata(
|
||||
classIds,
|
||||
selectorIds,
|
||||
dfsOrderClassIds,
|
||||
callableMemberIds,
|
||||
updateableFunctionsInMain,
|
||||
mainModuleTranslatorOptions,
|
||||
mainModuleEnvironment);
|
||||
}
|
||||
|
||||
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 verifyDynamicModuleOptions(WasmCompilerOptions options) {
|
||||
final translatorOptions = options.translatorOptions;
|
||||
|
||||
Never fail(String optionName) {
|
||||
throw StateError(
|
||||
'Inconsistent flag for dynamic module compilation: $optionName');
|
||||
}
|
||||
|
||||
// TODO(natebiggs): Disallow certain flags from being used in conjunction
|
||||
// with dynamic modules.
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,16 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:vm/metadata/procedure_attributes.dart'
|
||||
show ProcedureAttributesMetadataRepository;
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
import 'class_info.dart';
|
||||
import 'closures.dart';
|
||||
import 'code_generator.dart';
|
||||
import 'dispatch_table.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'intrinsics.dart';
|
||||
import 'reference_extensions.dart';
|
||||
import 'translator.dart';
|
||||
|
||||
@@ -33,6 +37,12 @@ class FunctionCollector {
|
||||
// if an allocation of that class is encountered
|
||||
final Map<int, List<Reference>> _pendingAllocation = {};
|
||||
|
||||
/// Collection of references marked as callable from dynamic modules.
|
||||
Set<Reference> dynamicModuleCallable = {};
|
||||
|
||||
late final WasmFunctionImporter _importedDynamicModuleFunctions =
|
||||
WasmFunctionImporter(translator, '#dmf');
|
||||
|
||||
FunctionCollector(this.translator);
|
||||
|
||||
void _collectImportsAndExports() {
|
||||
@@ -80,9 +90,32 @@ class FunctionCollector {
|
||||
/// name.
|
||||
String? getExportName(Reference target) => _exports[target];
|
||||
|
||||
w.BaseFunction importFunctionToDynamicModule(w.BaseFunction fun) {
|
||||
assert(translator.isDynamicModule);
|
||||
assert(fun.enclosingModule == translator.mainModule);
|
||||
if (!_importedDynamicModuleFunctions.has(fun)) {
|
||||
throw StateError('Function not callable from dynamic module: $fun');
|
||||
}
|
||||
return _importedDynamicModuleFunctions.get(fun, translator.dynamicModule);
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
_collectImportsAndExports();
|
||||
|
||||
if (translator.dynamicModuleSupportEnabled) {
|
||||
// We have to mark any class which can be constructed in a dynamic
|
||||
// module as allocated.
|
||||
for (final library in translator.libraries) {
|
||||
for (final cls in library.classes) {
|
||||
if (!cls.isAbstract &&
|
||||
cls.constructors.any(
|
||||
(c) => c.isDynamicModuleCallable(translator.coreTypes))) {
|
||||
recordClassAllocation(translator.classInfo[cls]!.classId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add exports to the module and add exported functions to the
|
||||
// compilationQueue.
|
||||
for (var export in _exports.entries) {
|
||||
@@ -108,6 +141,10 @@ class FunctionCollector {
|
||||
}
|
||||
}
|
||||
|
||||
if (translator.dynamicModuleSupportEnabled) {
|
||||
_generateDynamicModuleCallableReferences();
|
||||
}
|
||||
|
||||
// Value classes are always implicitly allocated.
|
||||
recordClassAllocation(
|
||||
translator.classInfo[translator.boxedBoolClass]!.classId);
|
||||
@@ -117,6 +154,98 @@ class FunctionCollector {
|
||||
translator.classInfo[translator.boxedDoubleClass]!.classId);
|
||||
}
|
||||
|
||||
void _generateDynamicModuleCallableReferences() {
|
||||
final references = dynamicModuleCallable = translator.isDynamicModule
|
||||
? translator.dynamicModuleInfo!.callableReferences!.toSet()
|
||||
: _generateCallableReferences();
|
||||
|
||||
for (final reference in references) {
|
||||
final member = reference.asMember;
|
||||
|
||||
if (member.isInstanceMember) {
|
||||
final selector = translator.dispatchTable.selectorForTarget(reference);
|
||||
final targetRanges = selector
|
||||
.targets(unchecked: false, dynamicModule: false)
|
||||
.targetRanges
|
||||
.followedBy(selector
|
||||
.targets(unchecked: true, dynamicModule: false)
|
||||
.targetRanges);
|
||||
// Instance members are only callable if their enclosing class is
|
||||
// allocated.
|
||||
for (final (:range, :target) in targetRanges) {
|
||||
if (target != reference) continue;
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
_recordClassTargetUse(classId, target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Generate static members immediately since they are unconditionally
|
||||
// callable.
|
||||
getFunction(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<Reference> _generateCallableReferences() {
|
||||
assert(
|
||||
translator.dynamicModuleSupportEnabled && !translator.isDynamicModule);
|
||||
|
||||
final exports = <Reference>{};
|
||||
void collectCallableReference(Reference reference) {
|
||||
final member = reference.asMember;
|
||||
|
||||
if (member.isExternal) {
|
||||
final isGeneratedIntrinsic = member is Procedure &&
|
||||
MemberIntrinsic.fromProcedure(translator.coreTypes, member) != null;
|
||||
if (!isGeneratedIntrinsic) return;
|
||||
}
|
||||
exports.add(reference);
|
||||
}
|
||||
|
||||
final procedureAttributeMetadata =
|
||||
(translator.component.metadata["vm.procedure-attributes.metadata"]
|
||||
as ProcedureAttributesMetadataRepository)
|
||||
.mapping;
|
||||
void collectCallableReferences(Member member) {
|
||||
if (member is Procedure) {
|
||||
collectCallableReference(member.reference);
|
||||
if (member.isInstanceMember &&
|
||||
member.kind == ProcedureKind.Method &&
|
||||
procedureAttributeMetadata[member]!.hasTearOffUses) {
|
||||
collectCallableReference(member.tearOffReference);
|
||||
}
|
||||
} else if (member is Field) {
|
||||
collectCallableReference(member.getterReference);
|
||||
if (member.hasSetter) {
|
||||
collectCallableReference(member.setterReference!);
|
||||
}
|
||||
} else if (member is Constructor) {
|
||||
if (translator.classInfo[member.enclosingClass]!.struct
|
||||
.isSubtypeOf(translator.objectInfo.struct)) {
|
||||
collectCallableReference(member.reference);
|
||||
collectCallableReference(member.initializerReference);
|
||||
collectCallableReference(member.constructorBodyReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final lib in translator.libraries) {
|
||||
for (final member in lib.members) {
|
||||
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
|
||||
collectCallableReferences(member);
|
||||
}
|
||||
|
||||
for (final cls in lib.classes) {
|
||||
for (final member in cls.members) {
|
||||
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
|
||||
collectCallableReferences(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
w.BaseFunction? getExistingFunction(Reference target) {
|
||||
return _functions[target];
|
||||
}
|
||||
@@ -128,6 +257,15 @@ class FunctionCollector {
|
||||
translator.signatureForDirectCall(target), getFunctionName(target));
|
||||
translator.compilationQueue.add(AstCompilationTask(function,
|
||||
getMemberCodeGenerator(translator, function, target), target));
|
||||
|
||||
// Export the function from the main module if it is callable from dynamic
|
||||
// modules.
|
||||
if (translator.dynamicModuleSupportEnabled &&
|
||||
dynamicModuleCallable.contains(target)) {
|
||||
assert(translator.dynamicModuleSupportEnabled);
|
||||
_importedDynamicModuleFunctions.get(function, translator.dynamicModule,
|
||||
exportOnly: true);
|
||||
}
|
||||
return function;
|
||||
});
|
||||
}
|
||||
@@ -177,8 +315,7 @@ class FunctionCollector {
|
||||
if (target.isTearOffReference) {
|
||||
assert(!translator.dispatchTable
|
||||
.selectorForTarget(target)
|
||||
.targetSet
|
||||
.contains(target));
|
||||
.containsTarget(target));
|
||||
return translator.signatureForDirectCall(target);
|
||||
}
|
||||
|
||||
@@ -236,25 +373,44 @@ class FunctionCollector {
|
||||
final set =
|
||||
useUncheckedEntry ? _calledUncheckedSelectors : _calledSelectors;
|
||||
if (set.add(selector.id)) {
|
||||
for (final (:range, :target)
|
||||
in selector.targets(unchecked: useUncheckedEntry).targetRanges) {
|
||||
for (final (:range, :target) in selector
|
||||
.targets(unchecked: useUncheckedEntry, dynamicModule: false)
|
||||
.targetRanges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
if (_allocatedClasses.contains(classId)) {
|
||||
// Class declaring or inheriting member is allocated somewhere.
|
||||
getFunction(target);
|
||||
} else {
|
||||
// Remember the member in case an allocation is encountered later.
|
||||
_pendingAllocation.putIfAbsent(classId, () => []).add(target);
|
||||
_recordClassTargetUse(classId, target);
|
||||
}
|
||||
}
|
||||
|
||||
if (translator.isDynamicModule) {
|
||||
for (final (:range, :target) in selector
|
||||
.targets(unchecked: useUncheckedEntry, dynamicModule: true)
|
||||
.targetRanges) {
|
||||
for (int classId = range.start; classId <= range.end; ++classId) {
|
||||
_recordClassTargetUse(classId, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recordClassAllocation(int classId) {
|
||||
if (_allocatedClasses.add(classId)) {
|
||||
void _recordClassTargetUse(int classId, Reference target) {
|
||||
if (_allocatedClasses.contains(classId)) {
|
||||
// Class declaring or inheriting member is allocated somewhere.
|
||||
getFunction(target);
|
||||
} else {
|
||||
// Remember the member in case an allocation is encountered later.
|
||||
_pendingAllocation.putIfAbsent(classId, () => []).add(target);
|
||||
}
|
||||
}
|
||||
|
||||
void recordClassAllocation(ClassId classId) {
|
||||
final id = switch (classId) {
|
||||
RelativeClassId() => classId.relativeValue,
|
||||
AbsoluteClassId() => classId.value,
|
||||
};
|
||||
if (_allocatedClasses.add(id)) {
|
||||
// Schedule all members that were pending allocation of this class.
|
||||
for (Reference target in _pendingAllocation[classId] ?? const []) {
|
||||
for (Reference target in _pendingAllocation[id] ?? const []) {
|
||||
getFunction(target);
|
||||
}
|
||||
}
|
||||
@@ -278,8 +434,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
}
|
||||
assert(!translator.dispatchTable
|
||||
.selectorForTarget(target)
|
||||
.targetSet
|
||||
.contains(target));
|
||||
.containsTarget(target));
|
||||
|
||||
final receiverType = target.asMember.enclosingClass!
|
||||
.getThisType(translator.coreTypes, Nullability.nonNullable);
|
||||
@@ -289,15 +444,16 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
|
||||
@override
|
||||
w.FunctionType visitProcedure(Procedure node, Reference target) {
|
||||
assert(!node.isAbstract);
|
||||
// Compilations for dynamic modules can contain interface calls to methods
|
||||
// that are not implemented yet.
|
||||
assert(!node.isAbstract || translator.dynamicModuleSupportEnabled);
|
||||
if (!node.isInstanceMember) {
|
||||
return _makeFunctionType(translator, target, null);
|
||||
}
|
||||
|
||||
assert(!translator.dispatchTable
|
||||
.selectorForTarget(target)
|
||||
.targetSet
|
||||
.contains(target));
|
||||
.containsTarget(target));
|
||||
|
||||
final receiverType = target.asMember.enclosingClass!
|
||||
.getThisType(translator.coreTypes, Nullability.nonNullable);
|
||||
|
||||
@@ -114,7 +114,10 @@ Future<int> generateWasm(WasmCompilerOptions options,
|
||||
await Future.wait(writeFutures);
|
||||
|
||||
final jsFile = path.setExtension(options.outputFile, '.mjs');
|
||||
await File(jsFile).writeAsString(result.jsRuntime);
|
||||
final jsRuntime = result.jsRuntime;
|
||||
if (jsRuntime != null) {
|
||||
await File(jsFile).writeAsString(jsRuntime);
|
||||
}
|
||||
|
||||
final supportJsFile = path.setExtension(options.outputFile, '.support.js');
|
||||
await File(supportJsFile).writeAsString(result.supportJs);
|
||||
|
||||
@@ -76,7 +76,8 @@ class Globals {
|
||||
final module = translator.moduleForReference(field.fieldReference);
|
||||
final memberName = field.toString();
|
||||
if (init != null &&
|
||||
!(translator.constants.ensureConstant(init)?.isLazy ?? false)) {
|
||||
!(translator.constants.ensureConstant(init, module)?.isLazy ??
|
||||
false)) {
|
||||
// Initialized to a constant
|
||||
final global = module.globals.define(
|
||||
w.GlobalType(fieldType, mutable: !field.isFinal), memberName);
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'abi.dart' show kWasmAbiEnumIndex;
|
||||
import 'class_info.dart';
|
||||
import 'code_generator.dart';
|
||||
import 'dynamic_forwarders.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'translator.dart';
|
||||
import 'types.dart';
|
||||
import 'util.dart';
|
||||
@@ -187,7 +188,8 @@ enum StaticIntrinsic {
|
||||
storeFloat('dart:ffi', null, '_storeFloat'),
|
||||
storeFloatUnaligned('dart:ffi', null, '_storeFloatUnaligned'),
|
||||
storeDouble('dart:ffi', null, '_storeDouble'),
|
||||
storeDoubleUnaligned('dart:ffi', null, '_storeDoubleUnaligned');
|
||||
storeDoubleUnaligned('dart:ffi', null, '_storeDoubleUnaligned'),
|
||||
;
|
||||
|
||||
final String library;
|
||||
final String? cls;
|
||||
@@ -764,6 +766,10 @@ class Intrinsifier {
|
||||
|
||||
// ClassID getters
|
||||
if (cls?.name == 'ClassID') {
|
||||
if (target.name.text == 'maxClassId') {
|
||||
codeGen.b.i32_const(translator.classIdNumbering.maxClassId);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
final libAndClassName = translator.getPragma(target, "wasm:class-id");
|
||||
if (libAndClassName != null) {
|
||||
List<String> libAndClassNameParts = libAndClassName.split("#");
|
||||
@@ -778,8 +784,8 @@ class Intrinsifier {
|
||||
orElse: () =>
|
||||
throw 'Class $className not found in library $lib '
|
||||
'(${target.location})');
|
||||
int classId = translator.classInfo[cls]!.classId;
|
||||
b.i32_const(classId);
|
||||
ClassId classId = translator.classInfo[cls]!.classId;
|
||||
b.pushClassIdToStack(translator, classId);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
|
||||
@@ -790,6 +796,13 @@ class Intrinsifier {
|
||||
}
|
||||
}
|
||||
|
||||
if (target.enclosingLibrary.name == 'dart._internal') {
|
||||
if (target.name.text == '_numClassesForConstCaches') {
|
||||
b.i64_const(translator.classIdNumbering.maxClassId);
|
||||
return w.NumType.i64;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
|
||||
switch (target.name.text) {
|
||||
case "_isIntrinsified":
|
||||
@@ -801,41 +814,12 @@ class Intrinsifier {
|
||||
case "_noSubstitutionIndex":
|
||||
b.i32_const(RuntimeTypeInformation.noSubstitutionIndex);
|
||||
return w.NumType.i32;
|
||||
case "_typeRowDisplacementOffsets":
|
||||
final type = translator
|
||||
.translateStorageType(
|
||||
types.rtt.typeRowDisplacementOffsets.interfaceType)
|
||||
.unpacked;
|
||||
case "_mainModuleRtt":
|
||||
final moduleRttType = translator.translateType(
|
||||
InterfaceType(translator.moduleRtt, Nullability.nonNullable));
|
||||
translator.constants.instantiateConstant(
|
||||
b, types.rtt.typeRowDisplacementOffsets, type);
|
||||
return type;
|
||||
case "_typeRowDisplacementTable":
|
||||
final type = translator
|
||||
.translateStorageType(
|
||||
types.rtt.typeRowDisplacementTable.interfaceType)
|
||||
.unpacked;
|
||||
translator.constants
|
||||
.instantiateConstant(b, types.rtt.typeRowDisplacementTable, type);
|
||||
return type;
|
||||
case "_typeRowDisplacementSubstTable":
|
||||
final type = translator
|
||||
.translateStorageType(
|
||||
types.rtt.typeRowDisplacementSubstTable.interfaceType)
|
||||
.unpacked;
|
||||
translator.constants.instantiateConstant(
|
||||
b, types.rtt.typeRowDisplacementSubstTable, type);
|
||||
return type;
|
||||
case "_typeNames":
|
||||
final type = translator
|
||||
.translateStorageType(types.rtt.typeNames.interfaceType)
|
||||
.unpacked;
|
||||
if (translator.options.minify) {
|
||||
b.ref_null((type as w.RefType).heapType);
|
||||
} else {
|
||||
translator.constants
|
||||
.instantiateConstant(b, types.rtt.typeNames, type);
|
||||
}
|
||||
return type;
|
||||
b, translator.types.rtt.mainModuleRtt, moduleRttType);
|
||||
return moduleRttType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,8 +1049,9 @@ class Intrinsifier {
|
||||
case StaticIntrinsic.isObjectClassId:
|
||||
final classId = node.arguments.positional.single;
|
||||
|
||||
final objectClassId = translator
|
||||
.classIdNumbering.classIds[translator.coreTypes.objectClass]!;
|
||||
final objectClassId = (translator.classIdNumbering
|
||||
.classIds[translator.coreTypes.objectClass] as AbsoluteClassId)
|
||||
.value;
|
||||
|
||||
codeGen.translateExpression(classId, w.NumType.i32);
|
||||
b.emitClassIdRangeCheck([Range(objectClassId, objectClassId)]);
|
||||
@@ -1075,21 +1060,54 @@ class Intrinsifier {
|
||||
final classId = node.arguments.positional.single;
|
||||
|
||||
final ranges = translator.classIdNumbering
|
||||
.getConcreteClassIdRanges(translator.coreTypes.functionClass);
|
||||
.getConcreteClassIdRangeForMainModule(
|
||||
translator.coreTypes.functionClass);
|
||||
assert(ranges.length <= 1);
|
||||
|
||||
codeGen.translateExpression(classId, w.NumType.i32);
|
||||
b.emitClassIdRangeCheck(ranges);
|
||||
|
||||
return w.NumType.i32;
|
||||
case StaticIntrinsic.isRecordClassId:
|
||||
final classId = node.arguments.positional.single;
|
||||
|
||||
final ranges = translator.classIdNumbering
|
||||
.getConcreteClassIdRanges(translator.coreTypes.recordClass);
|
||||
.getConcreteClassIdRangeForMainModule(
|
||||
translator.coreTypes.recordClass);
|
||||
assert(ranges.length <= 1);
|
||||
|
||||
codeGen.translateExpression(classId, w.NumType.i32);
|
||||
b.emitClassIdRangeCheck(ranges);
|
||||
if (translator.dynamicModuleSupportEnabled) {
|
||||
final dynamicModuleRanges = translator.classIdNumbering
|
||||
.getConcreteClassIdRangeForDynamicModule(
|
||||
translator.coreTypes.recordClass);
|
||||
final classIdLocal = b.addLocal(w.NumType.i32);
|
||||
codeGen.translateExpression(classId, w.NumType.i32);
|
||||
b.local_tee(classIdLocal);
|
||||
b.local_get(classIdLocal);
|
||||
translator.dynamicModuleInfo!.callClassIdBranchBuiltIn(
|
||||
BuiltinUpdatableFunctions.recordId,
|
||||
b,
|
||||
translator.typesBuilder
|
||||
.defineFunction(const [w.NumType.i32], const [w.NumType.i32]),
|
||||
skipDynamic: dynamicModuleRanges.isEmpty,
|
||||
buildMainMatch: (w.FunctionBuilder f) {
|
||||
final ib = f.body;
|
||||
ib.local_get(ib.locals[0]);
|
||||
ib.emitClassIdRangeCheck(ranges);
|
||||
ib.end();
|
||||
}, buildDynamicMatch: (w.FunctionBuilder f) {
|
||||
final ib = f.body;
|
||||
if (dynamicModuleRanges.isEmpty) {
|
||||
ib.i32_const(0);
|
||||
} else {
|
||||
ib.local_get(ib.locals[0]);
|
||||
ib.emitClassIdRangeCheck(dynamicModuleRanges);
|
||||
}
|
||||
ib.end();
|
||||
});
|
||||
} else {
|
||||
codeGen.translateExpression(classId, w.NumType.i32);
|
||||
b.emitClassIdRangeCheck(ranges);
|
||||
}
|
||||
return w.NumType.i32;
|
||||
|
||||
// dart:_object_helper static functions.
|
||||
@@ -1438,13 +1456,13 @@ class Intrinsifier {
|
||||
case StaticIntrinsic.isSubClassOf:
|
||||
final baseClass =
|
||||
(node.arguments.types.single as InterfaceType).classNode;
|
||||
final range =
|
||||
translator.classIdNumbering.getConcreteSubclassRange(baseClass);
|
||||
final ranges =
|
||||
translator.classIdNumbering.getConcreteSubclassRanges(baseClass);
|
||||
|
||||
final object = node.arguments.positional.single;
|
||||
codeGen.translateExpression(object, w.RefType.any(nullable: false));
|
||||
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
||||
b.emitClassIdRangeCheck([range]);
|
||||
b.emitClassIdRangeCheck(ranges);
|
||||
return w.NumType.i32;
|
||||
}
|
||||
}
|
||||
@@ -1604,7 +1622,7 @@ class Intrinsifier {
|
||||
|
||||
// Both bool?
|
||||
b.local_get(cid);
|
||||
b.i32_const(boolInfo.classId);
|
||||
b.i32_const((boolInfo.classId as AbsoluteClassId).value);
|
||||
b.i32_eq();
|
||||
b.if_();
|
||||
b.local_get(first);
|
||||
@@ -1619,7 +1637,7 @@ class Intrinsifier {
|
||||
|
||||
// Both int?
|
||||
b.local_get(cid);
|
||||
b.i32_const(intInfo.classId);
|
||||
b.i32_const((intInfo.classId as AbsoluteClassId).value);
|
||||
b.i32_eq();
|
||||
b.if_();
|
||||
b.local_get(first);
|
||||
@@ -1634,7 +1652,7 @@ class Intrinsifier {
|
||||
|
||||
// Both double?
|
||||
b.local_get(cid);
|
||||
b.i32_const(doubleInfo.classId);
|
||||
b.i32_const((doubleInfo.classId as AbsoluteClassId).value);
|
||||
b.i32_eq();
|
||||
b.if_();
|
||||
b.local_get(first);
|
||||
@@ -1666,7 +1684,8 @@ class Intrinsifier {
|
||||
final w.Local nonNullArg =
|
||||
b.addLocal(translator.topInfo.nonNullableType);
|
||||
final List<int> classIds = translator.valueClasses.keys
|
||||
.map((cls) => translator.classInfo[cls]!.classId)
|
||||
.map((cls) =>
|
||||
(translator.classInfo[cls]!.classId as AbsoluteClassId).value)
|
||||
.toList()
|
||||
..sort();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class CompiledApp {
|
||||
// wasm file produced by the dart2wasm compiler and returns the bytes to
|
||||
// load the module. These bytes can be in either a format supported by
|
||||
// `WebAssembly.compile` or `WebAssembly.compileStreaming`.
|
||||
async instantiate(additionalImports, {loadDeferredWasm} = {}) {
|
||||
async instantiate(additionalImports, {loadDeferredWasm, loadDynamicModule} = {}) {
|
||||
let dartInstance;
|
||||
|
||||
// Prints to the console
|
||||
@@ -111,12 +111,18 @@ class CompiledApp {
|
||||
|
||||
<<JS_STRING_POLYFILL_METHODS>>
|
||||
|
||||
const deferredLibraryHelper = {
|
||||
"loadModule": async (moduleName) => {
|
||||
if (!loadDeferredWasm) {
|
||||
throw "No implementation of loadDeferredWasm provided.";
|
||||
}
|
||||
const source = await Promise.resolve(loadDeferredWasm(moduleName));
|
||||
const loadModuleFromBytes = async (bytes) => {
|
||||
const module = await WebAssembly.compile(bytes, this.builtins);
|
||||
return await WebAssembly.instantiate(module, {
|
||||
...baseImports,
|
||||
...additionalImports,
|
||||
"wasm:js-string": jsStringPolyfill,
|
||||
"module0": dartInstance.exports,
|
||||
});
|
||||
}
|
||||
|
||||
const loadModule = async (loader, loaderArgument) => {
|
||||
const source = await Promise.resolve(loader(loaderArgument));
|
||||
const module = await ((source instanceof Response)
|
||||
? WebAssembly.compileStreaming(source, this.builtins)
|
||||
: WebAssembly.compile(source, this.builtins));
|
||||
@@ -126,6 +132,25 @@ class CompiledApp {
|
||||
<<JS_POLYFILL_IMPORT>>
|
||||
"module0": dartInstance.exports,
|
||||
});
|
||||
}
|
||||
|
||||
const deferredLibraryHelper = {
|
||||
"loadModule": async (moduleName) => {
|
||||
if (!loadDeferredWasm) {
|
||||
throw "No implementation of loadDeferredWasm provided.";
|
||||
}
|
||||
return await loadModule(loadDeferredWasm, moduleName);
|
||||
},
|
||||
"loadDynamicModuleFromUri": async (uri) => {
|
||||
if (!loadDynamicModule) {
|
||||
throw "No implementation of loadDynamicModule provided.";
|
||||
}
|
||||
const loadedModule = await loadModule(loadDynamicModule, uri);
|
||||
return loadedModule.exports.$invokeEntryPoint;
|
||||
},
|
||||
"loadDynamicModuleFromBytes": async (bytes) => {
|
||||
const loadedModule = await loadModuleFromBytes(loadDynamicModule, uri);
|
||||
return loadedModule.exports.$invokeEntryPoint;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/library_index.dart';
|
||||
|
||||
/// Kernel nodes for classes and members referenced specifically by the
|
||||
@@ -12,6 +13,8 @@ mixin KernelNodes {
|
||||
|
||||
LibraryIndex get index;
|
||||
|
||||
CoreTypes get coreTypes;
|
||||
|
||||
// dart:_internal classes
|
||||
late final Class symbolClass = index.getClass("dart:_internal", "Symbol");
|
||||
|
||||
@@ -121,6 +124,17 @@ mixin KernelNodes {
|
||||
index.getField("dart:core", "_RecordType", "fieldTypes");
|
||||
late final Field recordTypeNamesField =
|
||||
index.getField("dart:core", "_RecordType", "names");
|
||||
late final Class moduleRtt = index.getClass("dart:core", "_ModuleRtt");
|
||||
late final Field moduleRttOffsets =
|
||||
index.getField("dart:core", "_ModuleRtt", "typeRowDisplacementOffsets");
|
||||
late final Field moduleRttDisplacementTable =
|
||||
index.getField("dart:core", "_ModuleRtt", "typeRowDisplacementTable");
|
||||
late final Field moduleRttSubstTable = index.getField(
|
||||
"dart:core", "_ModuleRtt", "typeRowDisplacementSubstTable");
|
||||
late final Field moduleRttTypeNames =
|
||||
index.getField("dart:core", "_ModuleRtt", "typeNames");
|
||||
late final Procedure registerModuleRtt =
|
||||
index.getTopLevelProcedure("dart:core", "_registerModuleRtt");
|
||||
|
||||
// dart:core sync* support classes
|
||||
late final Class suspendStateClass =
|
||||
@@ -383,6 +397,50 @@ mixin KernelNodes {
|
||||
late final Procedure systemHashCombine =
|
||||
index.getProcedure("dart:_internal", "SystemHash", "combine");
|
||||
|
||||
// Dynamic module helpers
|
||||
late final Class constCacheClass =
|
||||
index.getClass('dart:_internal', 'WasmConstCache');
|
||||
late final Constructor constCacheInit =
|
||||
index.getConstructor('dart:_internal', 'WasmConstCache', '');
|
||||
late final Procedure constCacheCanonicalize = index.getProcedure(
|
||||
'dart:_internal', 'WasmConstCache', 'canonicalizeValue');
|
||||
late final Procedure constCacheArrayCanonicalize = index.getProcedure(
|
||||
'dart:_internal', 'WasmArrayConstCache', 'canonicalizeArrayValue');
|
||||
late final Procedure registerUpdateableFuncRefs = index.getTopLevelProcedure(
|
||||
'dart:_internal', 'registerUpdateableFuncRefs');
|
||||
late final Procedure getUpdateableFuncRef =
|
||||
index.getTopLevelProcedure('dart:_internal', 'getUpdateableFuncRef');
|
||||
late final Procedure classIdToModuleId =
|
||||
index.getTopLevelProcedure('dart:_internal', 'classIdToModuleId');
|
||||
late final Procedure localizeClassId =
|
||||
index.getTopLevelProcedure('dart:_internal', 'localizeClassId');
|
||||
late final Procedure scopeClassId =
|
||||
index.getTopLevelProcedure('dart:_internal', 'scopeClassId');
|
||||
late final Procedure globalizeClassId =
|
||||
index.getTopLevelProcedure('dart:_internal', 'globalizeClassId');
|
||||
late final Procedure registerModuleClassRange =
|
||||
index.getTopLevelProcedure('dart:_internal', 'registerModuleClassRange');
|
||||
late final Procedure constCacheGetter =
|
||||
index.getTopLevelProcedure('dart:_internal', 'getConstCache');
|
||||
late final Field objectConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'objectConstArray');
|
||||
late final Field stringConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'stringConstArray');
|
||||
late final Field stringConstImmutableArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'stringConstImmutableArray');
|
||||
late final Field typeConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'typeConstArray');
|
||||
late final Field typeArrayConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'typeArrayConstArray');
|
||||
late final Field namedParameterConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'nameParameterConstArray');
|
||||
late final Field i8ConstImmutableArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'i8ConstImmutableArray');
|
||||
late final Field i32ConstArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'i32ConstArray');
|
||||
late final Field i64ConstImmutableArrayCache =
|
||||
index.getTopLevelField('dart:_internal', 'i64ConstImmutableArray');
|
||||
|
||||
// Debugging
|
||||
late final Procedure printToConsole =
|
||||
index.getTopLevelProcedure("dart:_internal", "printToConsole");
|
||||
@@ -390,6 +448,29 @@ mixin KernelNodes {
|
||||
late final Map<Member, (Extension, ExtensionMemberDescriptor)>
|
||||
_extensionCache = {};
|
||||
|
||||
late final Map<InterfaceType, Field> wasmArrayConstCache = {
|
||||
_makeElementType(coreTypes.objectClass, nullable: true):
|
||||
objectConstArrayCache,
|
||||
_makeElementType(typeClass): typeConstArrayCache,
|
||||
_makeElementType(namedParameterClass): namedParameterConstArrayCache,
|
||||
_makeElementType(coreTypes.stringClass): stringConstArrayCache,
|
||||
_makeElementType(wasmI32Class): i32ConstArrayCache,
|
||||
_makeElementType(wasmArrayClass,
|
||||
typeArguments: [_makeElementType(typeClass)]): typeArrayConstArrayCache,
|
||||
};
|
||||
late final Map<InterfaceType, Field> immutableWasmArrayConstCache = {
|
||||
_makeElementType(coreTypes.stringClass): stringConstImmutableArrayCache,
|
||||
_makeElementType(wasmI8Class): i8ConstImmutableArrayCache,
|
||||
_makeElementType(wasmI64Class): i64ConstImmutableArrayCache,
|
||||
};
|
||||
|
||||
InterfaceType _makeElementType(Class c,
|
||||
{bool nullable = false, List<InterfaceType>? typeArguments}) =>
|
||||
InterfaceType(
|
||||
c,
|
||||
nullable ? Nullability.nullable : Nullability.nonNullable,
|
||||
typeArguments);
|
||||
|
||||
(Extension, ExtensionMemberDescriptor) extensionOfMember(Member member) {
|
||||
return _extensionCache.putIfAbsent(member, () {
|
||||
assert(member.isExtensionMember);
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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 'package:kernel/ast.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
|
||||
import 'target.dart';
|
||||
import 'util.dart';
|
||||
|
||||
const _mainModuleId = 0;
|
||||
|
||||
Library? _enclosingLibraryForReference(Reference reference) {
|
||||
TreeNode? current = reference.node;
|
||||
// References generated for constants will not have a node attached.
|
||||
if (reference.node == null) return null;
|
||||
while (current != null) {
|
||||
if (current is Library) return current;
|
||||
current = current.parent;
|
||||
}
|
||||
throw ArgumentError('Could not find enclosing library for ${reference.node}');
|
||||
}
|
||||
|
||||
Class? enclosingClassForReference(Reference reference) {
|
||||
TreeNode? current = reference.node;
|
||||
// References generated for constants will not have a node attached.
|
||||
if (reference.node == null) return null;
|
||||
while (current != null) {
|
||||
if (current is Class) return current;
|
||||
current = current.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ModuleOutputBuilder {
|
||||
int _counter = _mainModuleId;
|
||||
|
||||
ModuleOutput buildModule({bool emitAsMain = false, bool skipEmit = false}) =>
|
||||
ModuleOutput._(_counter++, emitAsMain: emitAsMain, skipEmit: skipEmit);
|
||||
}
|
||||
|
||||
/// Deferred loading metadata for a single dart2wasm output module.
|
||||
///
|
||||
/// Each [ModuleOutput] will map to a single wasm module emitted by the
|
||||
/// compiler. The separation of modules is guided by the deferred imports
|
||||
/// defined in the source code.
|
||||
///
|
||||
/// A module may contain code at any level of granularity. Code may be grouped
|
||||
/// by library, by class or neither. [containsReference] should be used to
|
||||
/// determine if a module contains a given class/member reference.
|
||||
class ModuleOutput {
|
||||
/// The ID for the module which will be included in the emitted name.
|
||||
final int _id;
|
||||
|
||||
/// The set of libraries contained in this module.
|
||||
final Set<Library> libraries = {};
|
||||
|
||||
bool get isMain => _id == _mainModuleId;
|
||||
|
||||
/// The name used to import and export this module.
|
||||
String get moduleImportName => 'module$_id';
|
||||
|
||||
/// The name added to the wasm output file for this module.
|
||||
final String moduleName;
|
||||
|
||||
/// Whether or not a wasm file should be emitted for this module.
|
||||
final bool skipEmit;
|
||||
|
||||
ModuleOutput._(this._id, {this.skipEmit = false, bool emitAsMain = false})
|
||||
: moduleName = emitAsMain || _id == _mainModuleId ? '' : 'module$_id';
|
||||
|
||||
/// Whether or not the provided kernel [Reference] is included in this module.
|
||||
bool containsReference(Reference reference) {
|
||||
final enclosingLibrary = _enclosingLibraryForReference(reference);
|
||||
if (enclosingLibrary == null) return false;
|
||||
return libraries.contains(enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$moduleImportName($libraries)';
|
||||
}
|
||||
|
||||
/// Data needed to create deferred modules.
|
||||
class ModuleOutputData {
|
||||
/// All [ModuleOutput]s generated for the program.
|
||||
final List<ModuleOutput> modules;
|
||||
|
||||
final Map<Library, Map<String, List<ModuleOutput>>> _importMap;
|
||||
|
||||
ModuleOutputData(this.modules, this._importMap) : assert(modules[0].isMain);
|
||||
|
||||
ModuleOutput get mainModule => modules[0];
|
||||
Iterable<ModuleOutput> get deferredModules => modules.skip(1);
|
||||
|
||||
bool get hasMultipleModules => modules.length > 1;
|
||||
|
||||
/// Mapping from deferred library import to the 'load list' of module names
|
||||
/// needed for that import.
|
||||
///
|
||||
/// If library L is required (either directly or indirectly) by two separate
|
||||
/// imports, then L will be in its own module. That module will be included in
|
||||
/// the load list for both those imports.
|
||||
Map<String, Map<String, List<String>>> generateModuleImportMap() {
|
||||
final result = <String, Map<String, List<String>>>{};
|
||||
_importMap.forEach((lib, importMapping) {
|
||||
final nameMapping = <String, List<String>>{};
|
||||
importMapping.forEach((importName, modules) {
|
||||
nameMapping[importName] =
|
||||
modules.map((o) => o.moduleImportName).toList();
|
||||
});
|
||||
result[lib.importUri.toString()] = nameMapping;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns the module that contains [reference].
|
||||
ModuleOutput moduleForReference(Reference reference) =>
|
||||
modules.firstWhere((e) => e.containsReference(reference));
|
||||
}
|
||||
|
||||
/// Module strategy that puts all libraries into a single module.
|
||||
class DefaultModuleStrategy extends ModuleStrategy {
|
||||
final Component component;
|
||||
|
||||
DefaultModuleStrategy(this.component);
|
||||
|
||||
@override
|
||||
ModuleOutputData buildModuleOutputData() {
|
||||
// If deferred loading is not enabled then put every library in the main
|
||||
// module.
|
||||
final mainModule = ModuleOutput._(_mainModuleId);
|
||||
mainModule.libraries.addAll(component.libraries);
|
||||
return ModuleOutputData([mainModule], const {});
|
||||
}
|
||||
|
||||
@override
|
||||
void prepareComponent() {}
|
||||
}
|
||||
|
||||
bool _hasWasmExportPragma(CoreTypes coreTypes, Member m) =>
|
||||
hasPragma(coreTypes, m, 'wasm:export');
|
||||
|
||||
bool containsWasmExport(CoreTypes coreTypes, Library lib) {
|
||||
if (lib.members.any((m) => _hasWasmExportPragma(coreTypes, m))) {
|
||||
return true;
|
||||
}
|
||||
return lib.classes
|
||||
.any((c) => c.members.any((m) => _hasWasmExportPragma(coreTypes, m)));
|
||||
}
|
||||
|
||||
abstract class ModuleStrategy {
|
||||
void prepareComponent();
|
||||
ModuleOutputData buildModuleOutputData();
|
||||
}
|
||||
|
||||
Set<Library> getReachableLibraries(
|
||||
Library entryPoint, CoreTypes coreTypes, WasmTarget kernelTarget) {
|
||||
final List<Library> queue = [entryPoint];
|
||||
final Set<Library> reachable = {entryPoint};
|
||||
while (queue.isNotEmpty) {
|
||||
final current = queue.removeLast();
|
||||
for (final dep in current.dependencies) {
|
||||
final importedLib = dep.targetLibrary;
|
||||
if (reachable.add(importedLib)) {
|
||||
queue.add(importedLib);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reachable;
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
// 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 'package:args/args.dart';
|
||||
import 'package:front_end/src/api_unstable/vm.dart' show resolveInputUri;
|
||||
|
||||
@@ -106,6 +104,6 @@ class UriMultiOption extends MultiValueOption<Uri> {
|
||||
UriMultiOption(
|
||||
name, void Function(WasmCompilerOptions o, List<Uri> v) applyToOptions,
|
||||
{Iterable<String>? defaultsTo})
|
||||
: super(name, applyToOptions, (v) => Uri.file(Directory(v).absolute.path),
|
||||
: super(name, applyToOptions, (v) => Uri.base.resolve(v),
|
||||
defaultsTo: defaultsTo);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,14 @@ class _RecordClassGenerator {
|
||||
|
||||
Library get library => coreTypes.coreLibrary;
|
||||
|
||||
late final Map<String, Class> _existingCoreClassNames = (() {
|
||||
final map = <String, Class>{};
|
||||
for (final cls in library.classes) {
|
||||
map[cls.name] = cls;
|
||||
}
|
||||
return map;
|
||||
})();
|
||||
|
||||
_RecordClassGenerator(this.classes, this.coreTypes);
|
||||
|
||||
void generateClassForRecordType(RecordType recordType) {
|
||||
@@ -198,6 +206,11 @@ class _RecordClassGenerator {
|
||||
className = '${className}_${shape.names.join('_')}';
|
||||
}
|
||||
|
||||
// If this is a dynamic module the loaded main module may already contain
|
||||
// this class.
|
||||
final existingClass = _existingCoreClassNames[className];
|
||||
if (existingClass != null) return existingClass;
|
||||
|
||||
final cls = addWasmEntryPointPragma(
|
||||
Class(
|
||||
name: className,
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class BinaryDataSink {
|
||||
static const int _initSinkSize = 50 * 1024;
|
||||
|
||||
Uint8List _data = Uint8List(_initSinkSize);
|
||||
int _length = 0;
|
||||
|
||||
BinaryDataSink();
|
||||
|
||||
int get length => _length;
|
||||
|
||||
void _ensure(int size) {
|
||||
// Ensure space for at least `size` additional bytes.
|
||||
if (_data.length < _length + size) {
|
||||
int newLength = _data.length * 2;
|
||||
while (newLength < _length + size) {
|
||||
newLength *= 2;
|
||||
}
|
||||
_data = Uint8List(newLength)..setRange(0, _data.length, _data);
|
||||
}
|
||||
}
|
||||
|
||||
void writeByte(int byte) {
|
||||
assert(byte == byte & 0xFF);
|
||||
_ensure(1);
|
||||
_data[_length++] = byte;
|
||||
}
|
||||
|
||||
void writeBytes(Uint8List bytes) {
|
||||
_ensure(bytes.length);
|
||||
_data.setRange(_length, _length += bytes.length, bytes);
|
||||
}
|
||||
|
||||
void writeString(String value) {
|
||||
final bytes = utf8.encode(value);
|
||||
writeInt(bytes.length);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
void writeBool(bool value) {
|
||||
writeByte(value ? 1 : 0);
|
||||
}
|
||||
|
||||
void writeInt(int value) {
|
||||
assert(value >= 0 && value >> 30 == 0);
|
||||
if (value < 0x80) {
|
||||
writeByte(value);
|
||||
} else if (value < 0x4000) {
|
||||
writeByte((value >> 8) | 0x80);
|
||||
writeByte(value & 0xFF);
|
||||
} else {
|
||||
writeByte((value >> 24) | 0xC0);
|
||||
writeByte((value >> 16) & 0xFF);
|
||||
writeByte((value >> 8) & 0xFF);
|
||||
writeByte(value & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
void writeClassId(int value) {
|
||||
// Add 1 since some class IDs are -1.
|
||||
writeInt(value + 1);
|
||||
}
|
||||
|
||||
void writeEnum<E extends Enum>(E value) {
|
||||
writeInt(value.index);
|
||||
}
|
||||
|
||||
Uint8List takeBytes() {
|
||||
final result = Uint8List.sublistView(_data, 0, _length);
|
||||
// Free the reference to the large data list so it can potentially be
|
||||
// tree-shaken.
|
||||
_data = Uint8List(0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryDataSource {
|
||||
int _byteOffset = 0;
|
||||
final Uint8List _bytes;
|
||||
|
||||
BinaryDataSource(this._bytes);
|
||||
|
||||
void begin(String tag) {}
|
||||
|
||||
void end(String tag) {}
|
||||
|
||||
int _readByte() => _bytes[_byteOffset++];
|
||||
|
||||
String readString() {
|
||||
int length = readInt();
|
||||
return utf8.decode(
|
||||
Uint8List.sublistView(_bytes, _byteOffset, _byteOffset += length));
|
||||
}
|
||||
|
||||
bool readBool() {
|
||||
return _readByte() != 0;
|
||||
}
|
||||
|
||||
int readInt() {
|
||||
var byte = _readByte();
|
||||
if (byte & 0x80 == 0) {
|
||||
// 0xxxxxxx
|
||||
return byte;
|
||||
} else if (byte & 0x40 == 0) {
|
||||
// 10xxxxxx
|
||||
return ((byte & 0x3F) << 8) | _readByte();
|
||||
} else {
|
||||
// 11xxxxxx
|
||||
return ((byte & 0x3F) << 24) |
|
||||
(_readByte() << 16) |
|
||||
(_readByte() << 8) |
|
||||
_readByte();
|
||||
}
|
||||
}
|
||||
|
||||
int readClassId() {
|
||||
// Subtract 1 since some class IDs are -1.
|
||||
return readInt() - 1;
|
||||
}
|
||||
|
||||
E readEnum<E extends Enum>(List<E> values) {
|
||||
int index = readInt();
|
||||
assert(
|
||||
0 <= index && index < values.length,
|
||||
"Invalid data kind index. "
|
||||
"Expected one of $values, found index $index.");
|
||||
return values[index];
|
||||
}
|
||||
|
||||
int get length => _bytes.length;
|
||||
int get currentOffset => _byteOffset;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ mixin SyncStarCodeGeneratorMixin on StateMachineEntryAstCodeGenerator {
|
||||
// function for this `sync*` function.
|
||||
DartType elementType = functionNode.emittedValueType!;
|
||||
translator.functions.recordClassAllocation(syncStarIterableInfo.classId);
|
||||
b.pushObjectHeaderFields(syncStarIterableInfo);
|
||||
b.pushObjectHeaderFields(translator, syncStarIterableInfo);
|
||||
types.makeType(this, elementType);
|
||||
if (context != null) {
|
||||
assert(!context.isEmpty);
|
||||
|
||||
@@ -19,15 +19,18 @@ import 'class_info.dart';
|
||||
import 'closures.dart';
|
||||
import 'code_generator.dart';
|
||||
import 'constants.dart';
|
||||
import 'deferred_loading.dart';
|
||||
import 'dispatch_table.dart';
|
||||
import 'dynamic_forwarders.dart';
|
||||
import 'dynamic_module_kernel_metadata.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'functions.dart';
|
||||
import 'globals.dart';
|
||||
import 'kernel_nodes.dart';
|
||||
import 'modules.dart';
|
||||
import 'param_info.dart';
|
||||
import 'records.dart';
|
||||
import 'reference_extensions.dart';
|
||||
import 'serialization.dart';
|
||||
import 'static_dispatch_table.dart';
|
||||
import 'tags.dart';
|
||||
import 'types.dart';
|
||||
@@ -57,6 +60,57 @@ class TranslatorOptions {
|
||||
int? sharedMemoryMaxPages;
|
||||
bool requireJsStringBuiltin = false;
|
||||
List<int> watchPoints = [];
|
||||
|
||||
void serialize(BinaryDataSink sink) {
|
||||
sink.writeBool(enableAsserts);
|
||||
sink.writeBool(importSharedMemory);
|
||||
sink.writeBool(inlining);
|
||||
sink.writeBool(jsCompatibility);
|
||||
sink.writeBool(omitImplicitTypeChecks);
|
||||
sink.writeBool(omitExplicitTypeChecks);
|
||||
sink.writeBool(omitBoundsChecks);
|
||||
sink.writeBool(polymorphicSpecialization);
|
||||
sink.writeBool(printKernel);
|
||||
sink.writeBool(printWasm);
|
||||
sink.writeBool(minify);
|
||||
sink.writeBool(verifyTypeChecks);
|
||||
sink.writeBool(verbose);
|
||||
sink.writeBool(enableExperimentalFfi);
|
||||
sink.writeBool(enableExperimentalWasmInterop);
|
||||
sink.writeBool(generateSourceMaps);
|
||||
sink.writeBool(enableDeferredLoading);
|
||||
sink.writeBool(enableMultiModuleStressTestMode);
|
||||
sink.writeInt(inliningLimit);
|
||||
sink.writeInt(
|
||||
sharedMemoryMaxPages == null ? 0 : (sharedMemoryMaxPages! + 1));
|
||||
}
|
||||
|
||||
static TranslatorOptions deserialize(BinaryDataSource source) {
|
||||
final TranslatorOptions options = TranslatorOptions();
|
||||
options.enableAsserts = source.readBool();
|
||||
options.importSharedMemory = source.readBool();
|
||||
options.inlining = source.readBool();
|
||||
options.jsCompatibility = source.readBool();
|
||||
options.omitImplicitTypeChecks = source.readBool();
|
||||
options.omitExplicitTypeChecks = source.readBool();
|
||||
options.omitBoundsChecks = source.readBool();
|
||||
options.polymorphicSpecialization = source.readBool();
|
||||
options.printKernel = source.readBool();
|
||||
options.printWasm = source.readBool();
|
||||
options.minify = source.readBool();
|
||||
options.verifyTypeChecks = source.readBool();
|
||||
options.verbose = source.readBool();
|
||||
options.enableExperimentalFfi = source.readBool();
|
||||
options.enableExperimentalWasmInterop = source.readBool();
|
||||
options.generateSourceMaps = source.readBool();
|
||||
options.enableDeferredLoading = source.readBool();
|
||||
options.enableMultiModuleStressTestMode = source.readBool();
|
||||
options.inliningLimit = source.readInt();
|
||||
final int sharedMemoryMaxPages = source.readInt();
|
||||
options.sharedMemoryMaxPages =
|
||||
sharedMemoryMaxPages == 0 ? null : (sharedMemoryMaxPages - 1);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/// The main entry point for the translation from kernel to Wasm and the hub for
|
||||
@@ -72,6 +126,7 @@ class Translator with KernelNodes {
|
||||
@override
|
||||
final Component component;
|
||||
final List<Library> libraries;
|
||||
@override
|
||||
final CoreTypes coreTypes;
|
||||
late final TypeEnvironment typeEnvironment;
|
||||
final ClosedWorldClassHierarchy hierarchy;
|
||||
@@ -151,7 +206,8 @@ class Translator with KernelNodes {
|
||||
final Set<Member> membersBeingGenerated = {};
|
||||
final Map<Reference, Closures> constructorClosures = {};
|
||||
late final w.FunctionBuilder initFunction;
|
||||
late final w.ValueType voidMarker;
|
||||
late final w.ValueType voidMarker =
|
||||
w.RefType.def(w.StructType("void"), nullable: true);
|
||||
// Lazily import FFI memory if used.
|
||||
late final w.Memory ffiMemory = mainModule.memories.import("ffi", "memory",
|
||||
options.importSharedMemory, 0, options.sharedMemoryMaxPages);
|
||||
@@ -164,9 +220,11 @@ class Translator with KernelNodes {
|
||||
final Map<w.StorageType, w.ArrayType> immutableArrayTypeCache = {};
|
||||
final Map<w.StorageType, w.ArrayType> mutableArrayTypeCache = {};
|
||||
final Map<w.BaseFunction, w.Global> functionRefCache = {};
|
||||
final Map<Procedure, ClosureImplementation> tearOffFunctionCache = {};
|
||||
final Map<Procedure, Map<w.ModuleBuilder, ClosureImplementation>>
|
||||
tearOffFunctionCache = {};
|
||||
|
||||
final Map<FunctionNode, ClosureImplementation> closureImplementations = {};
|
||||
final Map<FunctionNode, Map<w.ModuleBuilder, ClosureImplementation>>
|
||||
closureImplementations = {};
|
||||
|
||||
// Some convenience accessors for commonly used values.
|
||||
late final ClassInfo topInfo = classes[0];
|
||||
@@ -325,6 +383,11 @@ class Translator with KernelNodes {
|
||||
final Map<w.ModuleBuilder, ModuleOutput> _builderToOutput = {};
|
||||
bool get hasMultipleModules => _moduleOutputData.hasMultipleModules;
|
||||
|
||||
DynamicModuleInfo? dynamicModuleInfo;
|
||||
bool get dynamicModuleSupportEnabled => dynamicModuleInfo != null;
|
||||
bool get isDynamicModule => dynamicModuleInfo?.isDynamicModule ?? false;
|
||||
w.ModuleBuilder get dynamicModule => dynamicModuleInfo!.dynamicModule;
|
||||
|
||||
w.ModuleBuilder moduleForReference(Reference reference) =>
|
||||
_outputToBuilder[_moduleOutputData.moduleForReference(reference)]!;
|
||||
|
||||
@@ -343,7 +406,9 @@ class Translator with KernelNodes {
|
||||
: Closures(this, member, findCaptures: false);
|
||||
|
||||
Translator(this.component, this.coreTypes, this.index, this.recordClasses,
|
||||
this._moduleOutputData, this.options)
|
||||
this._moduleOutputData, this.options,
|
||||
{bool enableDynamicModules = false,
|
||||
DynamicModuleMetadata? dynamicModuleMetadata})
|
||||
: libraries = component.libraries,
|
||||
hierarchy =
|
||||
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy {
|
||||
@@ -357,6 +422,9 @@ class Translator with KernelNodes {
|
||||
functions = FunctionCollector(this);
|
||||
types = Types(this);
|
||||
exceptionTag = ExceptionTag(this);
|
||||
if (enableDynamicModules) {
|
||||
dynamicModuleInfo = DynamicModuleInfo(this, dynamicModuleMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
void _initLoadLibraryImportMap() {
|
||||
@@ -397,29 +465,36 @@ class Translator with KernelNodes {
|
||||
}
|
||||
}
|
||||
|
||||
void drainCompletionQueue() {
|
||||
while (!compilationQueue.isEmpty) {
|
||||
final task = compilationQueue.pop();
|
||||
task.run(this, options.printKernel, options.printWasm);
|
||||
}
|
||||
}
|
||||
|
||||
Map<ModuleOutput, w.Module> translate(
|
||||
Uri Function(String moduleName)? sourceMapUrlGenerator) {
|
||||
_initLoadLibraryImportMap();
|
||||
_initModules(sourceMapUrlGenerator);
|
||||
voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
|
||||
|
||||
closureLayouter.collect();
|
||||
classInfoCollector.collect();
|
||||
|
||||
initFunction = mainModule.functions
|
||||
.define(typesBuilder.defineFunction(const [], const []), "#init");
|
||||
mainModule.functions.start = initFunction;
|
||||
|
||||
closureLayouter.collect();
|
||||
classInfoCollector.collect();
|
||||
|
||||
globals = Globals(this);
|
||||
constants = Constants(this);
|
||||
|
||||
dispatchTable.build();
|
||||
|
||||
functions.initialize();
|
||||
while (!compilationQueue.isEmpty) {
|
||||
final task = compilationQueue.pop();
|
||||
task.run(this, options.printKernel, options.printWasm);
|
||||
}
|
||||
|
||||
dynamicModuleInfo?.initDynamicModule();
|
||||
|
||||
drainCompletionQueue();
|
||||
|
||||
dynamicModuleInfo?.finishDynamicModule();
|
||||
|
||||
constructorClosures.clear();
|
||||
dispatchTable.output();
|
||||
@@ -463,7 +538,12 @@ class Translator with KernelNodes {
|
||||
/// [callFunction].
|
||||
List<w.ValueType> callReference(
|
||||
Reference reference, w.InstructionsBuilder b) {
|
||||
return callFunction(functions.getFunction(reference), b);
|
||||
final function = functions.getFunction(reference);
|
||||
final targetModule = function.enclosingModule;
|
||||
if (targetModule == b.module) {
|
||||
return b.invoke(directCallTarget(reference));
|
||||
}
|
||||
return callFunction(function, b);
|
||||
}
|
||||
|
||||
late final WasmFunctionImporter _importedFunctions =
|
||||
@@ -476,9 +556,14 @@ class Translator with KernelNodes {
|
||||
List<w.ValueType> callFunction(
|
||||
w.BaseFunction function, w.InstructionsBuilder b) {
|
||||
final targetModule = function.enclosingModule;
|
||||
// TODO(natebiggs): Consider inlining function body in some scenarios.
|
||||
if (targetModule == b.module) {
|
||||
b.call(function);
|
||||
} else if (dynamicModuleSupportEnabled) {
|
||||
// This is a function that the dynamic interface spec has indicated is
|
||||
// callable from the dynamic module.
|
||||
final importedFunction =
|
||||
functions.importFunctionToDynamicModule(function);
|
||||
b.call(importedFunction);
|
||||
} else if (isMainModule(targetModule)) {
|
||||
final importedFunction = _importedFunctions.get(function, b.module);
|
||||
b.call(importedFunction);
|
||||
@@ -493,17 +578,28 @@ class Translator with KernelNodes {
|
||||
}
|
||||
|
||||
void callDispatchTable(w.InstructionsBuilder b, SelectorInfo selector,
|
||||
{required bool useUncheckedEntry}) {
|
||||
final offset = selector.targets(unchecked: useUncheckedEntry).offset;
|
||||
{Member? interfaceTarget, required bool useUncheckedEntry}) {
|
||||
if (dynamicModuleSupportEnabled && selector.isDynamicModuleOverrideable) {
|
||||
dynamicModuleInfo!.callUpdateableDispatch(b, selector, interfaceTarget!,
|
||||
useUncheckedEntry: useUncheckedEntry);
|
||||
} else {
|
||||
b.struct_get(topInfo.struct, FieldIndex.classId);
|
||||
final offset = selector
|
||||
.targets(unchecked: useUncheckedEntry, dynamicModule: false)
|
||||
.offset;
|
||||
if (offset == null) {
|
||||
b.unreachable();
|
||||
b.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(natebiggs): Handle dispatch to dynamic module overrideable members.
|
||||
b.struct_get(topInfo.struct, FieldIndex.classId);
|
||||
if (offset != 0) {
|
||||
b.i32_const(offset);
|
||||
b.i32_add();
|
||||
if (offset != 0) {
|
||||
b.i32_const(offset);
|
||||
b.i32_add();
|
||||
}
|
||||
b.call_indirect(selector.signature, dispatchTable.getWasmTable(b.module));
|
||||
b.emitUnreachableIfNoResult(selector.signature.outputs);
|
||||
}
|
||||
b.call_indirect(selector.signature, dispatchTable.getWasmTable(b.module));
|
||||
b.emitUnreachableIfNoResult(selector.signature.outputs);
|
||||
functions.recordSelectorUse(selector, useUncheckedEntry);
|
||||
}
|
||||
|
||||
@@ -515,6 +611,14 @@ class Translator with KernelNodes {
|
||||
: coreTypes.objectClass;
|
||||
}
|
||||
|
||||
void pushModuleId(w.InstructionsBuilder b) {
|
||||
if (!isDynamicModule || b.module != dynamicModule) {
|
||||
b.i64_const(0);
|
||||
} else {
|
||||
b.global_get(dynamicModuleInfo!.moduleIdGlobal);
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the runtime type of a tear-off. This is the signature of the
|
||||
/// method with the types of all covariant parameters replaced by `Object?`.
|
||||
FunctionType getTearOffType(Procedure method) {
|
||||
@@ -792,19 +896,25 @@ class Translator with KernelNodes {
|
||||
});
|
||||
}
|
||||
|
||||
ClosureImplementation getTearOffClosure(Procedure member) {
|
||||
return tearOffFunctionCache.putIfAbsent(member, () {
|
||||
ClosureImplementation getTearOffClosure(
|
||||
Procedure member, w.ModuleBuilder closureModule) {
|
||||
final innerCache = tearOffFunctionCache.putIfAbsent(member, () => {});
|
||||
return innerCache.putIfAbsent(closureModule, () {
|
||||
assert(member.kind == ProcedureKind.Method);
|
||||
final reference =
|
||||
getFunctionEntry(member.reference, uncheckedEntry: false);
|
||||
w.BaseFunction target = functions.getFunction(reference);
|
||||
return getClosure(member.function, target,
|
||||
return getClosure(member.function, target, closureModule,
|
||||
paramInfoForDirectCall(reference), "$member tear-off");
|
||||
});
|
||||
}
|
||||
|
||||
ClosureImplementation getClosure(FunctionNode functionNode,
|
||||
w.BaseFunction target, ParameterInfo paramInfo, String name) {
|
||||
ClosureImplementation getClosure(
|
||||
FunctionNode functionNode,
|
||||
w.BaseFunction target,
|
||||
w.ModuleBuilder closureModule,
|
||||
ParameterInfo paramInfo,
|
||||
String name) {
|
||||
// We compile a block multiple times in try-catch, to catch Dart exceptions
|
||||
// and then again to catch JS exceptions. We may also ask for
|
||||
// `ClosureImplementation` for a local function multiple times as we see
|
||||
@@ -817,13 +927,12 @@ class Translator with KernelNodes {
|
||||
// will be the value returned by `paramInfoForDirectCall`. So the key for
|
||||
// this cache can be just `FunctionNode`, instead of `(FunctionNode,
|
||||
// ParameterInfo)`.
|
||||
final existingImplementation = closureImplementations[functionNode];
|
||||
final existingImplementation =
|
||||
closureImplementations[functionNode]?[closureModule];
|
||||
if (existingImplementation != null) {
|
||||
return existingImplementation;
|
||||
}
|
||||
|
||||
final targetModule = target.enclosingModule;
|
||||
|
||||
// Look up the closure representation for the signature.
|
||||
int typeCount = functionNode.typeParameters.length;
|
||||
int positionalCount = functionNode.positionalParameters.length;
|
||||
@@ -895,7 +1004,7 @@ class Translator with KernelNodes {
|
||||
w.BaseFunction makeTrampoline(
|
||||
w.FunctionType signature, int posArgCount, List<String> argNames) {
|
||||
final trampoline =
|
||||
targetModule.functions.define(signature, "$name trampoline");
|
||||
closureModule.functions.define(signature, "$name trampoline");
|
||||
compilationQueue.add(CompilationTask(
|
||||
trampoline,
|
||||
_ClosureTrampolineGenerator(this, trampoline, target, typeCount,
|
||||
@@ -904,7 +1013,7 @@ class Translator with KernelNodes {
|
||||
}
|
||||
|
||||
w.BaseFunction makeDynamicCallEntry() {
|
||||
final function = targetModule.functions.define(
|
||||
final function = closureModule.functions.define(
|
||||
dynamicCallVtableEntryFunctionType, "$name dynamic call entry");
|
||||
compilationQueue.add(CompilationTask(
|
||||
function,
|
||||
@@ -927,7 +1036,7 @@ class Translator with KernelNodes {
|
||||
ib.ref_func(function);
|
||||
}
|
||||
|
||||
final vtable = targetModule.globals.define(w.GlobalType(
|
||||
final vtable = closureModule.globals.define(w.GlobalType(
|
||||
w.RefType.def(representation.vtableStruct, nullable: false),
|
||||
mutable: false));
|
||||
final ib = vtable.initializer;
|
||||
@@ -950,8 +1059,9 @@ class Translator with KernelNodes {
|
||||
ib.end();
|
||||
|
||||
final implementation = ClosureImplementation(representation, functions,
|
||||
dynamicCallEntry, vtable, targetModule, paramInfo);
|
||||
closureImplementations[functionNode] = implementation;
|
||||
dynamicCallEntry, vtable, closureModule, paramInfo);
|
||||
(closureImplementations[functionNode] ??= {})[closureModule] =
|
||||
implementation;
|
||||
return implementation;
|
||||
}
|
||||
|
||||
@@ -964,6 +1074,7 @@ class Translator with KernelNodes {
|
||||
}
|
||||
|
||||
void convertType(w.InstructionsBuilder b, w.ValueType from, w.ValueType to) {
|
||||
if (identical(from, to)) return;
|
||||
if (from == voidMarker || to == voidMarker) {
|
||||
if (from != voidMarker) {
|
||||
b.drop();
|
||||
@@ -1006,7 +1117,7 @@ class Translator with KernelNodes {
|
||||
|
||||
w.Local temp = b.addLocal(from);
|
||||
b.local_set(temp);
|
||||
b.i32_const(info.classId);
|
||||
b.i32_const((info.classId as AbsoluteClassId).value);
|
||||
b.local_get(temp);
|
||||
b.struct_new(info.struct);
|
||||
} else if (from is w.RefType) {
|
||||
@@ -1181,7 +1292,7 @@ class Translator with KernelNodes {
|
||||
w.FunctionType signatureForDirectCall(Reference target) {
|
||||
if (target.asMember.isInstanceMember && !target.isBodyReference) {
|
||||
final selector = dispatchTable.selectorForTarget(target);
|
||||
if (selector.targetSet.contains(target)) {
|
||||
if (selector.containsTarget(target)) {
|
||||
return selector.signature;
|
||||
}
|
||||
}
|
||||
@@ -1191,7 +1302,7 @@ class Translator with KernelNodes {
|
||||
ParameterInfo paramInfoForDirectCall(Reference target) {
|
||||
if (target.asMember.isInstanceMember) {
|
||||
final selector = dispatchTable.selectorForTarget(target);
|
||||
if (selector.targetSet.contains(target)) {
|
||||
if (selector.containsTarget(target)) {
|
||||
return selector.paramInfo;
|
||||
}
|
||||
}
|
||||
@@ -2166,8 +2277,11 @@ class PolymorphicDispatchers {
|
||||
|
||||
CallTarget getPolymorphicDispatcher(SelectorInfo selector,
|
||||
{required bool useUncheckedEntry}) {
|
||||
assert(
|
||||
selector.targets(unchecked: useUncheckedEntry).targetRanges.length > 1);
|
||||
assert(selector
|
||||
.targets(unchecked: useUncheckedEntry, dynamicModule: false)
|
||||
.targetRanges
|
||||
.length >
|
||||
1);
|
||||
return (useUncheckedEntry && selector.useMultipleEntryPoints
|
||||
? uncheckedCache
|
||||
: cache)
|
||||
@@ -2186,7 +2300,8 @@ class PolymorphicDispatcherCallTarget extends CallTarget {
|
||||
|
||||
PolymorphicDispatcherCallTarget(this.translator, this.selector,
|
||||
this.callingModule, this.useUncheckedEntry)
|
||||
: super(selector.signature);
|
||||
: assert(!selector.isDynamicModuleOverrideable),
|
||||
super(selector.signature);
|
||||
|
||||
@override
|
||||
String get name => '${selector.name} (polymorphic dispatcher)';
|
||||
@@ -2197,7 +2312,7 @@ class PolymorphicDispatcherCallTarget extends CallTarget {
|
||||
@override
|
||||
bool get shouldInline =>
|
||||
selector
|
||||
.targets(unchecked: useUncheckedEntry)
|
||||
.targets(unchecked: useUncheckedEntry, dynamicModule: false)
|
||||
.staticDispatchRanges
|
||||
.length <=
|
||||
2;
|
||||
@@ -2223,14 +2338,18 @@ class PolymorphicDispatcherCodeGenerator implements CodeGenerator {
|
||||
final bool useUncheckedEntry;
|
||||
|
||||
PolymorphicDispatcherCodeGenerator(
|
||||
this.translator, this.selector, this.useUncheckedEntry);
|
||||
this.translator, this.selector, this.useUncheckedEntry)
|
||||
: assert(!selector.isDynamicModuleOverrideable);
|
||||
|
||||
@override
|
||||
void generate(w.InstructionsBuilder b, List<w.Local> paramLocals,
|
||||
w.Label? returnLabel) {
|
||||
final signature = selector.signature;
|
||||
|
||||
final targets = selector.targets(unchecked: useUncheckedEntry);
|
||||
final targets = selector.targets(
|
||||
unchecked: useUncheckedEntry,
|
||||
dynamicModule:
|
||||
translator.isDynamicModule && b.module == translator.dynamicModule);
|
||||
|
||||
final targetRanges = targets.staticDispatchRanges
|
||||
.map((entry) => (range: entry.range, value: entry.target))
|
||||
@@ -2399,18 +2518,23 @@ abstract class _WasmImporter<T extends w.Exportable> {
|
||||
|
||||
Iterable<T> get imports => _map.values.expand((v) => v.values);
|
||||
|
||||
T get(T key, w.ModuleBuilder module) {
|
||||
T get(T key, w.ModuleBuilder module, {bool exportOnly = false}) {
|
||||
if (key.enclosingModule == module) return key;
|
||||
|
||||
final innerMap = _map.putIfAbsent(key, () {
|
||||
key.enclosingModule.exports.export('$_exportPrefix${_map.length}', key);
|
||||
return {};
|
||||
});
|
||||
if (exportOnly) return key;
|
||||
return innerMap.putIfAbsent(module, () {
|
||||
return _import(module, key,
|
||||
_translator.nameForModule(key.enclosingModule), key.exportedName);
|
||||
});
|
||||
}
|
||||
|
||||
bool has(T key) {
|
||||
return _map.containsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
class WasmFunctionImporter extends _WasmImporter<w.BaseFunction> {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
import 'class_info.dart';
|
||||
import 'code_generator.dart';
|
||||
import 'dispatch_table.dart' show Row, buildRowDisplacementTable;
|
||||
import 'dynamic_modules.dart';
|
||||
import 'translator.dart';
|
||||
|
||||
/// Values for the `_kind` field in `_TopType`. Must match the definitions in
|
||||
@@ -44,6 +45,10 @@ class Types {
|
||||
late final ClassInfo typeClassInfo =
|
||||
translator.classInfo[translator.typeClass]!;
|
||||
|
||||
/// Class info for `_NamedParameter`
|
||||
late final ClassInfo namedParameterClassInfo =
|
||||
translator.classInfo[translator.namedParameterClass]!;
|
||||
|
||||
/// Wasm value type of `List<_Type>`
|
||||
late final w.ValueType typeListExpectedType =
|
||||
translator.classInfo[translator.listBaseClass]!.nonNullableType;
|
||||
@@ -193,7 +198,7 @@ class Types {
|
||||
final b = codeGen.b;
|
||||
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
|
||||
b.i32_const(encodedNullability(type));
|
||||
b.i32_const(typeInfo.classId);
|
||||
b.pushClassIdToStack(translator, typeInfo.classId);
|
||||
_makeTypeArray(codeGen, type.typeArguments);
|
||||
}
|
||||
|
||||
@@ -346,7 +351,7 @@ class Types {
|
||||
}
|
||||
|
||||
translator.functions.recordClassAllocation(info.classId);
|
||||
b.pushObjectHeaderFields(info);
|
||||
b.pushObjectHeaderFields(translator, info);
|
||||
if (type is InterfaceType) {
|
||||
_makeInterfaceType(codeGen, type);
|
||||
} else if (type is FunctionType) {
|
||||
@@ -422,7 +427,7 @@ class Types {
|
||||
codeGen.call(translator.isNullabilityCheck.reference);
|
||||
} else {
|
||||
b.i32_const(encodedNullability(testedAgainstType));
|
||||
b.i32_const(typeClassInfo.classId);
|
||||
b.pushClassIdToStack(translator, typeClassInfo.classId);
|
||||
if (typeArguments.isEmpty) {
|
||||
codeGen.call(translator.isInterfaceSubtype0.reference);
|
||||
} else if (typeArguments.length == 1) {
|
||||
@@ -500,7 +505,7 @@ class Types {
|
||||
final typeClassInfo = translator.classInfo[testedAgainstType.classNode]!;
|
||||
final typeArguments = testedAgainstType.typeArguments;
|
||||
b.i32_const(encodedNullability(testedAgainstType));
|
||||
b.i32_const(typeClassInfo.classId);
|
||||
b.pushClassIdToStack(translator, typeClassInfo.classId);
|
||||
if (typeArguments.isEmpty) {
|
||||
outputsToDrop = codeGen.call(translator.asInterfaceSubtype0.reference);
|
||||
} else if (typeArguments.length == 1) {
|
||||
@@ -551,6 +556,10 @@ abstract class _TypeCheckers {
|
||||
if (testedAgainstType is! InterfaceType) {
|
||||
return (null, checkArguments: false);
|
||||
}
|
||||
if (testedAgainstType.classNode
|
||||
.isDynamicModuleExtendable(rtt.translator.coreTypes)) {
|
||||
return (null, checkArguments: false);
|
||||
}
|
||||
|
||||
if (_hasOnlyDefaultTypeArguments(testedAgainstType)) {
|
||||
return (testedAgainstType, checkArguments: false);
|
||||
@@ -726,7 +735,9 @@ class IsCheckerCallTarget extends CallTarget {
|
||||
this.testedAgainstType,
|
||||
this.operandIsNullable,
|
||||
this.checkArguments,
|
||||
this.argumentCount);
|
||||
this.argumentCount)
|
||||
: assert(!testedAgainstType.classNode
|
||||
.isDynamicModuleExtendable(translator.coreTypes));
|
||||
|
||||
@override
|
||||
String get name {
|
||||
@@ -755,8 +766,8 @@ class IsCheckerCallTarget extends CallTarget {
|
||||
|
||||
// Always inline single class-id range checks (no branching, simply loads,
|
||||
// arithmetic and unsigned compare).
|
||||
final ranges =
|
||||
translator.classIdNumbering.getConcreteClassIdRanges(interfaceClass);
|
||||
final ranges = translator.classIdNumbering
|
||||
.getConcreteClassIdRangeForCurrentModule(interfaceClass);
|
||||
return ranges.length <= 1;
|
||||
}
|
||||
|
||||
@@ -872,7 +883,7 @@ class IsCheckerCodeGenerator implements CodeGenerator {
|
||||
b.ref_test(translator.closureInfo.nonNullableType);
|
||||
} else {
|
||||
final ranges = translator.classIdNumbering
|
||||
.getConcreteClassIdRanges(interfaceClass);
|
||||
.getConcreteClassIdRangeForCurrentModule(interfaceClass);
|
||||
b.local_get(operand);
|
||||
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
||||
b.emitClassIdRangeCheck(ranges);
|
||||
@@ -955,7 +966,9 @@ class AsCheckerCodeGenerator implements CodeGenerator {
|
||||
this.testedAgainstType,
|
||||
this.operandIsNullable,
|
||||
this.checkArguments,
|
||||
this.argumentCount);
|
||||
this.argumentCount)
|
||||
: assert(!testedAgainstType.classNode
|
||||
.isDynamicModuleExtendable(translator.coreTypes));
|
||||
|
||||
@override
|
||||
void generate(w.InstructionsBuilder b, List<w.Local> paramLocals,
|
||||
@@ -984,7 +997,7 @@ class AsCheckerCodeGenerator implements CodeGenerator {
|
||||
translator.classInfo[testedAgainstType.classNode]!.classId;
|
||||
b.local_get(b.locals[0]);
|
||||
b.i32_const(encodedNullability(testedAgainstType));
|
||||
b.i32_const(testedAgainstClassId);
|
||||
b.pushClassIdToStack(translator, testedAgainstClassId);
|
||||
if (argumentCount == 1) {
|
||||
b.local_get(b.locals[1]);
|
||||
translator.callReference(
|
||||
@@ -1039,15 +1052,6 @@ class RuntimeTypeInformation {
|
||||
/// not have to substitute anything.
|
||||
static const int noSubstitutionIndex = 0;
|
||||
|
||||
/// Table of type names indexed by class id.
|
||||
late final InstanceConstant typeNames;
|
||||
|
||||
/// See sdk/lib/_internal/wasm/lib/type.dart:_typeRowDisplacement*
|
||||
/// for what this contains and how it's used for substitution.
|
||||
late final InstanceConstant typeRowDisplacementOffsets;
|
||||
late final InstanceConstant typeRowDisplacementTable;
|
||||
late final InstanceConstant typeRowDisplacementSubstTable;
|
||||
|
||||
CoreTypes get coreTypes => translator.coreTypes;
|
||||
Types get types => translator.types;
|
||||
|
||||
@@ -1056,24 +1060,25 @@ class RuntimeTypeInformation {
|
||||
late final Map<InstanceConstant, int> _substitutionTable;
|
||||
late final List<InstanceConstant> _substitutionTableByIndex;
|
||||
|
||||
/// Object containing RTT info for the main module. See
|
||||
/// sdk/lib/_internal/wasm/lib/type.dart for what this contains and how it's
|
||||
/// used.
|
||||
late final InstanceConstant mainModuleRtt = getModuleRtt(isMainModule: true);
|
||||
|
||||
final Map<int, bool> _requiresSubstitutionForSubclasses = {};
|
||||
|
||||
RuntimeTypeInformation(this.translator) {
|
||||
_buildTypeRules();
|
||||
|
||||
// Data structure to tell whether two types are related and if so how to
|
||||
// translate type arguments from one class to that of a super class.
|
||||
_initTypeRowDiplacementTable();
|
||||
|
||||
// The class name table of type WasmArray<String>
|
||||
_initTypeNames();
|
||||
}
|
||||
|
||||
bool requiresTypeArgumentSubstitution(Class superclass) {
|
||||
final superclassId = translator.classIdNumbering.classIds[superclass]!;
|
||||
return _requiresSubstitutionForSubclasses.putIfAbsent(superclassId, () {
|
||||
final subclassSubstitutions =
|
||||
_substitutionSuperclassToSubclass[superclassId];
|
||||
final id = switch (superclassId) {
|
||||
RelativeClassId() => superclassId.relativeValue,
|
||||
AbsoluteClassId() => superclassId.value,
|
||||
};
|
||||
return _requiresSubstitutionForSubclasses.putIfAbsent(id, () {
|
||||
final subclassSubstitutions = _substitutionSuperclassToSubclass[id];
|
||||
|
||||
if (subclassSubstitutions == null) return false;
|
||||
for (final entry in subclassSubstitutions.entries) {
|
||||
@@ -1146,11 +1151,22 @@ class RuntimeTypeInformation {
|
||||
substitutionIndex = index;
|
||||
}
|
||||
|
||||
final subclassId = translator.classInfo[subtype.classNode]!.classId;
|
||||
(_substitutionSubclassToSuperclass[subclassId] ??=
|
||||
{})[superclassInfo.classId] = substitutionIndex;
|
||||
(_substitutionSuperclassToSubclass[superclassInfo.classId] ??=
|
||||
{})[subclassId] = substitutionIndex;
|
||||
final subclassIdWrapped =
|
||||
translator.classInfo[subtype.classNode]!.classId;
|
||||
final subclassId = switch (subclassIdWrapped) {
|
||||
RelativeClassId() => subclassIdWrapped.relativeValue,
|
||||
AbsoluteClassId() => subclassIdWrapped.value,
|
||||
};
|
||||
final superclassIdWrapped = superclassInfo.classId;
|
||||
final superclassId = switch (superclassIdWrapped) {
|
||||
RelativeClassId() => superclassIdWrapped.relativeValue,
|
||||
AbsoluteClassId() => superclassIdWrapped.value,
|
||||
};
|
||||
|
||||
(_substitutionSubclassToSuperclass[subclassId] ??= {})[superclassId] =
|
||||
substitutionIndex;
|
||||
(_substitutionSuperclassToSubclass[superclassId] ??= {})[subclassId] =
|
||||
substitutionIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1184,7 +1200,7 @@ class RuntimeTypeInformation {
|
||||
return true;
|
||||
}
|
||||
|
||||
void _initTypeRowDiplacementTable() {
|
||||
InstanceConstant getModuleRtt({required bool isMainModule}) {
|
||||
final rowForSuperclass = List<Row?>.filled(translator.classes.length, null);
|
||||
final rows = <Row<(int, int)>>[];
|
||||
final ranges = _buildRanges(_substitutionSuperclassToSubclass);
|
||||
@@ -1217,42 +1233,60 @@ class RuntimeTypeInformation {
|
||||
|
||||
rows.sort((Row a, Row b) => -weight(a).compareTo(weight(b)));
|
||||
final table = buildRowDisplacementTable(rows, firstAvailable: 1);
|
||||
typeRowDisplacementTable = translator.constants.makeArrayOf(wasmI32, [
|
||||
final typeRowDisplacementTable = translator.constants.makeArrayOf(wasmI32, [
|
||||
for (final entry in table)
|
||||
translator.constants.makeWasmI32(entry == null
|
||||
? 0
|
||||
: (entry.$2 == noSubstitutionIndex ? -entry.$1 : entry.$1)),
|
||||
]);
|
||||
|
||||
typeRowDisplacementSubstTable =
|
||||
final typeRowDisplacementSubstTable =
|
||||
translator.constants.makeArrayOf(arrayOfType, [
|
||||
for (final entry in table)
|
||||
_substitutionTableByIndex[
|
||||
entry == null ? noSubstitutionIndex : entry.$2],
|
||||
]);
|
||||
|
||||
typeRowDisplacementOffsets = translator.constants.makeArrayOf(wasmI32, [
|
||||
final typeRowDisplacementOffsets =
|
||||
translator.constants.makeArrayOf(wasmI32, [
|
||||
for (int classId = 0; classId < translator.classes.length; ++classId)
|
||||
translator.constants
|
||||
.makeWasmI32(rowForSuperclass[classId]?.offset ?? -1),
|
||||
]);
|
||||
|
||||
final typeNames = translator.options.minify
|
||||
? NullConstant()
|
||||
: _getTypeNames(isMainModule);
|
||||
|
||||
return InstanceConstant(translator.moduleRtt.reference, const [], {
|
||||
translator.moduleRttOffsets.fieldReference: typeRowDisplacementOffsets,
|
||||
translator.moduleRttDisplacementTable.fieldReference:
|
||||
typeRowDisplacementTable,
|
||||
translator.moduleRttSubstTable.fieldReference:
|
||||
typeRowDisplacementSubstTable,
|
||||
translator.moduleRttTypeNames.fieldReference: typeNames,
|
||||
});
|
||||
}
|
||||
|
||||
void _initTypeNames() {
|
||||
InstanceConstant _getTypeNames(bool isMainModule) {
|
||||
final stringType =
|
||||
translator.coreTypes.stringRawType(Nullability.nonNullable);
|
||||
|
||||
final emptyString = StringConstant('');
|
||||
List<StringConstant> nameConstants = [];
|
||||
List<StringConstant> dynamicModuleNameConstants = [];
|
||||
for (ClassInfo classInfo in translator.classes) {
|
||||
Class? cls = classInfo.cls;
|
||||
if (cls == null || cls.isAnonymousMixin) {
|
||||
nameConstants.add(emptyString);
|
||||
} else {
|
||||
nameConstants.add(StringConstant(cls.name));
|
||||
final constantList = classInfo.classId is RelativeClassId
|
||||
? dynamicModuleNameConstants
|
||||
: nameConstants;
|
||||
constantList.add(StringConstant(cls.name));
|
||||
}
|
||||
}
|
||||
typeNames = translator.constants.makeArrayOf(stringType, nameConstants);
|
||||
return translator.constants.makeArrayOf(
|
||||
stringType, isMainModule ? nameConstants : dynamicModuleNameConstants);
|
||||
}
|
||||
|
||||
Map<int, List<(Range, int)>> _buildRanges(Map<int, Map<int, int>> map) {
|
||||
|
||||
@@ -33,8 +33,7 @@ Future<Object?> load(String moduleName,
|
||||
return loadModuleFromBytes(bytes);
|
||||
}
|
||||
// Dart2wasm implementation
|
||||
return loadModuleFromUri(
|
||||
Uri(scheme: '', path: 'modules/${moduleName}_module1.wasm'));
|
||||
return loadModuleFromUri(Uri(scheme: '', path: 'modules/$moduleName.wasm'));
|
||||
}
|
||||
|
||||
/// Notify the test harness that the test has run to completion.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
callable:
|
||||
- library: 'shared/shared.dart'
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
- library: 'dart:core'
|
||||
class: 'String'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'shared/shared.dart' as shared;
|
||||
|
||||
main() async {
|
||||
await helper.load('entry1.dart');
|
||||
Expect.equals('dynamic module 1: hello', shared.topLevelClosure!('hello'));
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
String Function(String s, {int i})? _localTopLevelClosure;
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
void dynamicModuleEntrypoint() {
|
||||
String f<T>(T s, {int? i}) => 'dynamic module 1: $s';
|
||||
String g(String s, {int? i}) => 'dynamic module 2: $s';
|
||||
_localTopLevelClosure = f<String>;
|
||||
topLevelClosure = _localTopLevelClosure;
|
||||
_localTopLevelClosure!('a', i: 1);
|
||||
_localTopLevelClosure = g;
|
||||
_localTopLevelClosure!('b', i: 2);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
String Function(String s)? topLevelClosure;
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
extendable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
|
||||
can-be-overridden:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: 'method1'
|
||||
|
||||
# TODO(sigmund): consider implying this for all extendable types.
|
||||
callable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: 'x'
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: 'method1'
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
class: 'num'
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'shared/shared.dart' show Base;
|
||||
|
||||
/// A dynamic module is allowed to extend a class in the dynamic interface and
|
||||
/// override its members.
|
||||
main() async {
|
||||
Expect.equals(100, Base().method1(0));
|
||||
final o1 = (await helper.load('entry1.dart'));
|
||||
final o2 = (await helper.load('entry2.dart'));
|
||||
Expect.equals(1, o1);
|
||||
Expect.equals(3, o2);
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class Child1 extends Base {
|
||||
@override
|
||||
int method1(int i) => i + 1;
|
||||
}
|
||||
|
||||
class Child2 extends Base {
|
||||
@override
|
||||
int method1(int i) => i + 2;
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() {
|
||||
Base.x = Child1();
|
||||
final x = Base.x;
|
||||
if (x is Base) {
|
||||
return x.method1(0);
|
||||
}
|
||||
throw 'bad';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class Child3 extends Base {
|
||||
@override
|
||||
int method1(int i) => i + 3;
|
||||
}
|
||||
|
||||
class Child4 extends Base {
|
||||
@override
|
||||
int method1(int i) => i + 4;
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() {
|
||||
Base.x = Child3();
|
||||
final x = Base.x;
|
||||
if (x is Base) {
|
||||
return x.method1(0);
|
||||
}
|
||||
throw 'bad';
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
class Base {
|
||||
int method1(int i) => i + 100;
|
||||
|
||||
static Object? x;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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.
|
||||
|
||||
callable:
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
class: 'bool'
|
||||
- library: 'dart:core'
|
||||
class: 'num'
|
||||
|
||||
extendable:
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
/// A dynamic module is allowed to extend a class in the dynamic interface and
|
||||
/// override its members.
|
||||
main() async {
|
||||
final o = (await helper.load('entry1.dart')) as int;
|
||||
Expect.equals(1, o);
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
abstract class Base {
|
||||
int method1();
|
||||
}
|
||||
|
||||
class Child extends Base {
|
||||
@override
|
||||
int method1() => 1;
|
||||
}
|
||||
|
||||
dynamic getChild(bool x) {
|
||||
if (x) {
|
||||
return Child();
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
bool getOpaqueTrue() => 5 < 20;
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() {
|
||||
final x = getChild(getOpaqueTrue());
|
||||
if (x is Base) {
|
||||
return x.method1();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
extendable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
|
||||
can-be-overridden:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: 'method1'
|
||||
|
||||
# TODO(sigmund): consider implying this for all extendable types.
|
||||
callable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: ''
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'shared/shared.dart' show Base, SuperBase;
|
||||
|
||||
/// A dynamic module is allowed to extend a class in the dynamic interface and
|
||||
/// override its members.
|
||||
main() async {
|
||||
final o = (await helper.load('entry1.dart')) as Base;
|
||||
if (o is SuperBase<num, String>) {
|
||||
Expect.equals(3, o.method1());
|
||||
} else {
|
||||
Expect.fail('Missed type check');
|
||||
}
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class Child<T> extends Base<T> {
|
||||
final T t;
|
||||
Child(this.t);
|
||||
|
||||
@override
|
||||
T method1() => t;
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => Child(3);
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
abstract class SuperBase<T, U> {}
|
||||
|
||||
abstract class Base<T> extends SuperBase<T, String> {
|
||||
T method1();
|
||||
}
|
||||
@@ -12,6 +12,11 @@ extendable:
|
||||
callable:
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'String'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
|
||||
@@ -7,8 +7,6 @@ import 'package:expect/expect.dart';
|
||||
import '../../common/testing.dart' as helper;
|
||||
import 'modules/common.dart';
|
||||
|
||||
// It is an error to load a module that provides a second definition for
|
||||
// a library that already exists in the application.
|
||||
main() async {
|
||||
final a1 = await helper.load('entry1.dart') as A;
|
||||
final a2 = await helper.load('entry2.dart') as A;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
extendable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
|
||||
can-be-overridden:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: 'method1'
|
||||
|
||||
# TODO(sigmund): consider implying this for all extendable types.
|
||||
callable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'Base'
|
||||
member: ''
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
class: 'num'
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'shared/shared.dart' show Base;
|
||||
|
||||
/// A dynamic module is allowed to extend a class in the dynamic interface and
|
||||
/// override its members.
|
||||
main() async {
|
||||
final o = (await helper.load('entry1.dart')) as Base;
|
||||
Expect.equals(1, o.method1(1));
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class Child extends Base {
|
||||
@override
|
||||
int method1(int i, [int? j]) => i + (j ?? 0);
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => Child();
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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.
|
||||
|
||||
abstract class Base {
|
||||
int method1(int i) => i;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
extendable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: ['A', 'B']
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
|
||||
can-be-overridden:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'A'
|
||||
member: 'foo'
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'B'
|
||||
member: 'foo'
|
||||
|
||||
# TODO(sigmund): consider implying this for all extendable types.
|
||||
callable:
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
member: 'print'
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
|
||||
import 'shared/shared.dart' show A, B;
|
||||
|
||||
main() async {
|
||||
final o = (await helper.load('entry1.dart')) as A;
|
||||
final l = <dynamic>[A(), B()];
|
||||
for (final entry in l) {
|
||||
entry.foo();
|
||||
}
|
||||
o.foo();
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class C implements A, B {
|
||||
@override
|
||||
void foo() => print('C.foo');
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => C();
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
class A {
|
||||
void foo() => print('A.foo');
|
||||
}
|
||||
|
||||
class B {
|
||||
void foo() => print('B.foo');
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
callable:
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
class: 'int'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
// Similar to `isolated_shared`, constant canonicalization distinguishes
|
||||
// two constnats, even if they are created from a common library that was
|
||||
// not part of the original application.
|
||||
main() async {
|
||||
final c1 = (await helper.load('entry1.dart'));
|
||||
final c2 = (await helper.load('entry2.dart'));
|
||||
|
||||
Expect.equals(c1, c2);
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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.
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => (a: 3, 4);
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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.
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => (a: 3, 4);
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
extendable:
|
||||
- library: 'shared/shared.dart'
|
||||
class: ['C']
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
|
||||
can-be-overridden:
|
||||
- library: 'shared/shared.dart'
|
||||
class: 'C'
|
||||
member: 'foo'
|
||||
|
||||
# TODO(sigmund): consider implying this for all extendable types.
|
||||
callable:
|
||||
# TODO(sigmund): This should be included by default
|
||||
- library: 'dart:core'
|
||||
class: 'Object'
|
||||
- library: 'dart:core'
|
||||
class: 'pragma'
|
||||
member: '_'
|
||||
- library: 'dart:core'
|
||||
member: 'override'
|
||||
- library: 'dart:core'
|
||||
member: 'print'
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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 '../../common/testing.dart' as helper;
|
||||
|
||||
import 'shared/shared.dart' show A, B, C;
|
||||
|
||||
main() async {
|
||||
final c = await helper.load('entry1.dart') as C?;
|
||||
if (c != null) {
|
||||
A(B(c).c.foo);
|
||||
}
|
||||
helper.done();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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 '../shared/shared.dart';
|
||||
|
||||
class C1 implements C {
|
||||
@override
|
||||
void foo() {
|
||||
print('foo');
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('dyn-module:entry-point')
|
||||
Object? dynamicModuleEntrypoint() => C1();
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
abstract class C {
|
||||
void foo();
|
||||
}
|
||||
|
||||
class B {
|
||||
final C c;
|
||||
|
||||
B(this.c);
|
||||
}
|
||||
|
||||
class A {
|
||||
final void Function() f;
|
||||
A(this.f);
|
||||
}
|
||||
@@ -6066,21 +6066,19 @@ abstract class ErrorReporter {
|
||||
bool get hasSeenError;
|
||||
}
|
||||
|
||||
// Coverage-ignore(suite): Not run.
|
||||
class SimpleErrorReporter implements ErrorReporter {
|
||||
const SimpleErrorReporter();
|
||||
|
||||
@override
|
||||
// Coverage-ignore(suite): Not run.
|
||||
bool get supportsTrackingReportedErrors => false;
|
||||
|
||||
@override
|
||||
// Coverage-ignore(suite): Not run.
|
||||
bool get hasSeenError {
|
||||
return unsupported("SimpleErrorReporter.hasSeenError", -1, null);
|
||||
}
|
||||
|
||||
@override
|
||||
// Coverage-ignore(suite): Not run.
|
||||
void report(LocatedMessage message, [List<LocatedMessage>? context]) {
|
||||
_report(message);
|
||||
if (context != null) {
|
||||
@@ -6090,12 +6088,10 @@ class SimpleErrorReporter implements ErrorReporter {
|
||||
}
|
||||
}
|
||||
|
||||
// Coverage-ignore(suite): Not run.
|
||||
void _report(LocatedMessage message) {
|
||||
reportMessage(message.uri, message.charOffset, message.problemMessage);
|
||||
}
|
||||
|
||||
// Coverage-ignore(suite): Not run.
|
||||
void reportMessage(Uri? uri, int offset, String message) {
|
||||
io.exitCode = 42;
|
||||
io.stderr.writeln('$uri:$offset Constant evaluation error: $message');
|
||||
|
||||
@@ -41,7 +41,8 @@ Component loadComponentSourceFromBytes(Uint8List bytes,
|
||||
return component;
|
||||
}
|
||||
|
||||
Future writeComponentToBinary(Component component, String path) {
|
||||
Future writeComponentToBinary(Component component, String path,
|
||||
{bool includeSource = true}) {
|
||||
IOSink sink;
|
||||
if (path == 'null' || path == 'stdout') {
|
||||
sink = stdout.nonBlocking;
|
||||
@@ -51,7 +52,8 @@ Future writeComponentToBinary(Component component, String path) {
|
||||
|
||||
Future future;
|
||||
try {
|
||||
new BinaryPrinter(sink).writeComponentFile(component);
|
||||
new BinaryPrinter(sink, includeSources: includeSource)
|
||||
.writeComponentFile(component);
|
||||
} finally {
|
||||
if (sink == stdout.nonBlocking) {
|
||||
future = sink.flush();
|
||||
|
||||
@@ -6,14 +6,13 @@ import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/type_algebra.dart';
|
||||
|
||||
/// De-duplication of identical mixin applications.
|
||||
void transformComponent(Component component) {
|
||||
void transformLibraries(List<Library> libraries) {
|
||||
final deduplicateMixins = new DeduplicateMixinsTransformer();
|
||||
final referenceUpdater = ReferenceUpdater(deduplicateMixins);
|
||||
|
||||
// Deduplicate mixins and re-resolve super initializers.
|
||||
// (this is a shallow transformation)
|
||||
component.libraries
|
||||
.forEach((library) => deduplicateMixins.visitLibrary(library, null));
|
||||
libraries.forEach((library) => deduplicateMixins.visitLibrary(library, null));
|
||||
|
||||
// Do a deep transformation to update references to the removed mixin
|
||||
// application classes in the interface targets and types.
|
||||
@@ -32,7 +31,12 @@ void transformComponent(Component component) {
|
||||
// TODO(dartbug.com/39375): Remove this extra O(N) pass over the AST if the
|
||||
// CFE decides to consistently let the interface target point to the mixin
|
||||
// class (instead of mixin application).
|
||||
component.libraries.forEach(referenceUpdater.visitLibrary);
|
||||
libraries.forEach(referenceUpdater.visitLibrary);
|
||||
}
|
||||
|
||||
/// De-duplication of identical mixin applications.
|
||||
void transformComponent(Component component) {
|
||||
transformLibraries(component.libraries);
|
||||
}
|
||||
|
||||
class _DeduplicateMixinKey {
|
||||
|
||||
@@ -97,6 +97,30 @@ class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
|
||||
|
||||
ConstantPragmaAnnotationParser(this.coreTypes, this.target);
|
||||
|
||||
ParsedEntryPointPragma? getEntryPointTypeFromOptions(
|
||||
Constant options, String pragmaName) {
|
||||
PragmaEntryPointType? type;
|
||||
if (options is NullConstant) {
|
||||
type = PragmaEntryPointType.Default;
|
||||
} else if (options is BoolConstant && options.value == true) {
|
||||
type = PragmaEntryPointType.Default;
|
||||
} else if (options is StringConstant) {
|
||||
if (options.value == "get") {
|
||||
type = PragmaEntryPointType.GetterOnly;
|
||||
} else if (options.value == "set") {
|
||||
type = PragmaEntryPointType.SetterOnly;
|
||||
} else if (options.value == "call") {
|
||||
type = PragmaEntryPointType.CallOnly;
|
||||
} else {
|
||||
throw "Error: string directive to "
|
||||
"@pragma('$pragmaName', ...) "
|
||||
"must be either 'get' or 'set' for fields "
|
||||
"or 'get' or 'call' for procedures.";
|
||||
}
|
||||
}
|
||||
return type != null ? ParsedEntryPointPragma(type) : null;
|
||||
}
|
||||
|
||||
ParsedPragma? parsePragma(Expression annotation) {
|
||||
InstanceConstant? pragmaConstant;
|
||||
if (annotation is ConstantExpression) {
|
||||
@@ -131,26 +155,7 @@ class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
|
||||
|
||||
switch (pragmaName) {
|
||||
case kVmEntryPointPragmaName:
|
||||
PragmaEntryPointType? type;
|
||||
if (options is NullConstant) {
|
||||
type = PragmaEntryPointType.Default;
|
||||
} else if (options is BoolConstant && options.value == true) {
|
||||
type = PragmaEntryPointType.Default;
|
||||
} else if (options is StringConstant) {
|
||||
if (options.value == "get") {
|
||||
type = PragmaEntryPointType.GetterOnly;
|
||||
} else if (options.value == "set") {
|
||||
type = PragmaEntryPointType.SetterOnly;
|
||||
} else if (options.value == "call") {
|
||||
type = PragmaEntryPointType.CallOnly;
|
||||
} else {
|
||||
throw "Error: string directive to "
|
||||
"@pragma('$kVmEntryPointPragmaName', ...) "
|
||||
"must be either 'get' or 'set' for fields "
|
||||
"or 'get' or 'call' for procedures.";
|
||||
}
|
||||
}
|
||||
return type != null ? ParsedEntryPointPragma(type) : null;
|
||||
return getEntryPointTypeFromOptions(options, pragmaName);
|
||||
case kVmExactResultTypePragmaName:
|
||||
if (options is TypeLiteralConstant) {
|
||||
return ParsedResultTypeByTypePragma(options.type, false);
|
||||
@@ -207,7 +212,7 @@ class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
|
||||
PragmaEntryPointType.CanBeOverridden);
|
||||
case kDynModuleCallablePragmaName:
|
||||
case kDynModuleImplicitlyCallablePragmaName:
|
||||
return const ParsedEntryPointPragma(PragmaEntryPointType.Default);
|
||||
return getEntryPointTypeFromOptions(options, pragmaName);
|
||||
case kDynModuleEntryPointPragmaName:
|
||||
return const ParsedDynModuleEntryPointPragma();
|
||||
default:
|
||||
|
||||
@@ -21,6 +21,15 @@ Component transformComponent(Target target, Component component,
|
||||
return component;
|
||||
}
|
||||
|
||||
List<Library> transformLibraries(Target target, List<Library> libraries,
|
||||
VMConstantEvaluator evaluator, bool enableAsserts) {
|
||||
for (final library in libraries) {
|
||||
SimpleUnreachableCodeElimination(evaluator, enableAsserts: enableAsserts)
|
||||
.visitLibrary(library, null);
|
||||
}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
class SimpleUnreachableCodeElimination extends RemovingTransformer {
|
||||
final bool enableAsserts;
|
||||
final VMConstantEvaluator constantEvaluator;
|
||||
|
||||
@@ -65,6 +65,7 @@ class FunctionsBuilder with Builder<ir.Functions> {
|
||||
/// Declare [function] as a module element so it can be used in a constant
|
||||
/// context.
|
||||
void declare(ir.BaseFunction function) {
|
||||
assert(function.enclosingModule == _module);
|
||||
_declaredFunctions.add(function);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class TableBuilder extends ir.Table with IndexableBuilder<ir.DefinedTable> {
|
||||
"Elements are only supported for funcref tables");
|
||||
assert(maxSize == null || index < maxSize!,
|
||||
'Index $index greater than max table size $maxSize');
|
||||
assert(function.enclosingModule == enclosingModule);
|
||||
if (index >= elements.length) {
|
||||
elements.length = index + 1;
|
||||
}
|
||||
|
||||
@@ -78,4 +78,73 @@ class ClassID {
|
||||
|
||||
// Dummy, only used by VM-specific hash table code.
|
||||
static final WasmI32 numPredefinedCids = 1.toWasmI32();
|
||||
|
||||
// The maximum class ID in the main module of the program.
|
||||
external static WasmI32 get maxClassId;
|
||||
}
|
||||
|
||||
const int mainModuleId = 0;
|
||||
|
||||
/// The ith entry in this array is the max global class ID for module i.
|
||||
WasmArray<WasmI32> _moduleMaxClassId = WasmArray.filled(1, ClassID.maxClassId);
|
||||
|
||||
/// Gets the module ID for a given global class ID.
|
||||
@pragma('dyn-module:callable')
|
||||
int classIdToModuleId(WasmI32 classId) {
|
||||
if (!hasDynamicModuleSupport) {
|
||||
assert(
|
||||
_moduleMaxClassId.length == 1 &&
|
||||
classId <= _moduleMaxClassId[mainModuleId],
|
||||
);
|
||||
return mainModuleId;
|
||||
}
|
||||
// NOTE: This could be made into binary search if many modules are getting
|
||||
// registered. For now we expect few so a linear search is fine.
|
||||
final array = _moduleMaxClassId;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (classId <= array[i]) return i;
|
||||
}
|
||||
throw ArgumentError();
|
||||
}
|
||||
|
||||
/// Registers a new dynamic module based on the size of its new class ID range.
|
||||
@pragma('dyn-module:callable')
|
||||
int registerModuleClassRange(WasmI32 rangeSize) {
|
||||
final oldRanges = _moduleMaxClassId;
|
||||
final moduleId = oldRanges.length;
|
||||
|
||||
final newRanges = WasmArray<WasmI32>.filled(moduleId + 1, 0.toWasmI32());
|
||||
newRanges.copy(0, oldRanges, 0, oldRanges.length);
|
||||
newRanges[moduleId] = rangeSize + oldRanges[moduleId - 1];
|
||||
_moduleMaxClassId = newRanges;
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
/// Scopes a class ID to the enclosing module giving an ID relative to only
|
||||
/// classes defined in the module defining the class.
|
||||
@pragma('dyn-module:callable')
|
||||
WasmI32 scopeClassId(WasmI32 classId) {
|
||||
final moduleId = classIdToModuleId(classId);
|
||||
if (moduleId == 0) return classId;
|
||||
return classId - (_moduleMaxClassId[moduleId - 1] + 1.toWasmI32());
|
||||
}
|
||||
|
||||
/// Produces a localized class ID from a global class ID. A local ID is offset
|
||||
/// relative to the main module rather than the global ID space. The compiler
|
||||
/// produces localized IDs since it can't track global IDs. Multiple classes
|
||||
/// can map to the same localized class ID.
|
||||
@pragma('dyn-module:callable')
|
||||
WasmI32 localizeClassId(WasmI32 classId) {
|
||||
final moduleId = classIdToModuleId(classId);
|
||||
if (moduleId == 0) return classId;
|
||||
return classId - _moduleMaxClassId[moduleId - 1] + ClassID.maxClassId;
|
||||
}
|
||||
|
||||
/// Produces a global class ID from a local class ID. A global ID is offset
|
||||
/// relative to all registered dynamic modules. Each class will have a unique
|
||||
/// global class ID.
|
||||
@pragma('dyn-module:callable')
|
||||
WasmI32 globalizeClassId(WasmI32 classId, int moduleId) {
|
||||
if (moduleId == 0) return classId;
|
||||
return classId - ClassID.maxClassId + _moduleMaxClassId[moduleId - 1];
|
||||
}
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
import "dart:_internal"
|
||||
show
|
||||
ClassID,
|
||||
classIdToModuleId,
|
||||
CodeUnits,
|
||||
doubleToIntBits,
|
||||
EfficientLengthIterable,
|
||||
FixedLengthListMixin,
|
||||
hasDynamicModuleSupport,
|
||||
intBitsToDouble,
|
||||
IterableElementError,
|
||||
jsonEncode,
|
||||
localizeClassId,
|
||||
ListIterator,
|
||||
Lists,
|
||||
mainModuleId,
|
||||
minify,
|
||||
mix64,
|
||||
patch,
|
||||
@@ -63,6 +67,8 @@ import 'dart:_string_helper';
|
||||
|
||||
import 'dart:_wasm';
|
||||
|
||||
import 'internal_patch.dart';
|
||||
|
||||
part "closure.dart";
|
||||
part "double_patch.dart";
|
||||
part "errors_patch.dart";
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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.
|
||||
|
||||
part of "internal_patch.dart";
|
||||
|
||||
@pragma("wasm:import", "deferredLibraryHelper.loadDynamicModuleFromUri")
|
||||
external WasmExternRef _loadModuleFromUri(WasmExternRef moduleUri);
|
||||
|
||||
@pragma("wasm:import", "deferredLibraryHelper.loadDynamicModuleFromBytes")
|
||||
external WasmExternRef _loadModuleFromBytes(WasmExternRef moduleUri);
|
||||
|
||||
@patch
|
||||
Future<Object?> loadDynamicModule({Uri? uri, Uint8List? bytes}) {
|
||||
JSPromise loadPromise;
|
||||
if (uri != null) {
|
||||
final uriString = '$uri';
|
||||
loadPromise =
|
||||
(_loadModuleFromUri(uriString.toJS.toExternRef!).toJS as JSPromise);
|
||||
} else if (bytes != null) {
|
||||
loadPromise =
|
||||
(_loadModuleFromBytes(bytes.buffer.toJS.toExternRef!).toJS
|
||||
as JSPromise);
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'Must provide either `uri` or `bytes` to `loadDynamicModule`',
|
||||
);
|
||||
}
|
||||
return loadPromise.toDart.then(
|
||||
(entryPoint) =>
|
||||
dartifyRaw(((entryPoint as JSFunction).callAsFunction())?.toExternRef),
|
||||
);
|
||||
}
|
||||
|
||||
/// Only classes defined in the main module require runtime canonicalization.
|
||||
/// Classes defined in the dynamic modules cannot be shared outside of that
|
||||
/// module so can be canonicalized at compile time. So this will always only be
|
||||
/// the number of classes in the main module.
|
||||
external int get _numClassesForConstCaches;
|
||||
|
||||
/// Stores caches to canonicalize instances of objects. The i'th entry contains
|
||||
/// a cache of entities for type Class_i where Class_i is the class with
|
||||
/// ID i.
|
||||
@pragma('dyn-module:callable')
|
||||
final WasmArray<WasmConstCache?> _constCacheByType = WasmArray.filled(
|
||||
_numClassesForConstCaches,
|
||||
null,
|
||||
);
|
||||
|
||||
@pragma('dyn-module:callable')
|
||||
WasmConstCache getConstCache(int classId) {
|
||||
return _constCacheByType[classId] ??= WasmConstCache();
|
||||
}
|
||||
|
||||
/// Runtime cache containing constant values for a particular class.
|
||||
///
|
||||
/// Contains growable wasm arrays to store values in. Doesn't use a Dart
|
||||
/// growable List since Lists require a runtime type to be instantiated. This
|
||||
/// would cause an instantiation loop since runtime types are constants.
|
||||
///
|
||||
/// Values of type WasmArray<T> are stored separately from values of type T,
|
||||
/// so that the appropriate equality function can be used to compare them.
|
||||
///
|
||||
/// Note: The functions in here avoid polymorphic helpers as this would require
|
||||
/// instantiating Type constants and we cannot use constants in code to create
|
||||
/// constants.
|
||||
class WasmConstCache {
|
||||
int _nextIndex = 0;
|
||||
WasmArray<Object> _data = WasmArray<Object>.filled(
|
||||
2,
|
||||
WasmAnyRef.fromObject(Object()),
|
||||
);
|
||||
|
||||
@pragma('dyn-module:callable')
|
||||
WasmConstCache();
|
||||
|
||||
@pragma('dyn-module:callable', 'call')
|
||||
Object canonicalizeValue(
|
||||
Object value,
|
||||
WasmFunction<bool Function(Object val1, Object val2)> check,
|
||||
) {
|
||||
for (int i = 0; i < _nextIndex; i++) {
|
||||
final cachedValue = _data[i];
|
||||
if (check.call(value, cachedValue)) {
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
if (_data.length == _nextIndex) {
|
||||
final newCache = WasmArray<Object>.filled(_data.length * 2, _data[0]);
|
||||
newCache.copy(0, _data, 0, _data.length);
|
||||
_data = newCache;
|
||||
}
|
||||
_data[_nextIndex++] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('dyn-module:callable')
|
||||
final objectConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final stringConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final stringConstImmutableArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final typeConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final typeArrayConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final nameParameterConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final i8ConstImmutableArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final i32ConstArray = WasmArrayConstCache();
|
||||
@pragma('dyn-module:callable')
|
||||
final i64ConstImmutableArray = WasmArrayConstCache();
|
||||
|
||||
class WasmArrayConstCache {
|
||||
// Guaranteed by construction to contain only arrays with the same type.
|
||||
WasmArray<WasmArrayRef>? _data;
|
||||
int _nextIndex = 0;
|
||||
|
||||
WasmArrayConstCache();
|
||||
|
||||
@pragma('dyn-module:callable', 'call')
|
||||
WasmArrayRef canonicalizeArrayValue(
|
||||
WasmArrayRef value,
|
||||
WasmFunction<bool Function(WasmArrayRef val1, WasmArrayRef val2)> check,
|
||||
) {
|
||||
var data =
|
||||
_data ??= WasmArray<WasmArrayRef>.filled(
|
||||
2,
|
||||
WasmArray.filled(0, WasmAnyRef.fromObject(Object())),
|
||||
);
|
||||
for (int i = 0; i < _nextIndex; i++) {
|
||||
final cachedValue = data[i];
|
||||
if (value.length != cachedValue.length) continue;
|
||||
// The cachedValue must be the second value here so that it's type is
|
||||
// verified.
|
||||
if (check.call(value, cachedValue)) {
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
if (data.length == _nextIndex) {
|
||||
final newCache = WasmArray<WasmArrayRef>.filled(data.length * 2, data[0]);
|
||||
newCache.copy(0, data, 0, data.length);
|
||||
data = _data = newCache;
|
||||
}
|
||||
data[_nextIndex++] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// A table where there is one row per module and each column represents the
|
||||
/// updateable function for the corresponding allocated key index. The compiler
|
||||
/// tracks updateable functions via a unique string key which is converted to an
|
||||
/// integer index at either compile time or runtime. Each module may have an
|
||||
/// implementation of that function key.
|
||||
WasmArray<WasmArray<WasmFuncRef?>> _updateableRefs = WasmArray.literal(
|
||||
const [],
|
||||
);
|
||||
|
||||
/// Get the function reference implementation for allocated index [key] as
|
||||
/// defined by module [moduleId].
|
||||
@pragma('dyn-module:callable')
|
||||
WasmFuncRef? getUpdateableFuncRef(int moduleId, int key) {
|
||||
final moduleRefs = _updateableRefs[moduleId];
|
||||
if (key >= moduleRefs.length) return null;
|
||||
return moduleRefs[key];
|
||||
}
|
||||
|
||||
/// Register the updateable function ref implementations into a new module.
|
||||
/// [refs] contains implementations of pre-allocated (i.e. defined in main)
|
||||
/// keys. [stringKeys] and [stringRefs] contain runtime allocated refs and their
|
||||
/// keys. If any of the entries in [stringKeys] are new, an index will be
|
||||
/// allocated for them.
|
||||
@pragma('dyn-module:callable')
|
||||
void registerUpdateableFuncRefs(WasmArray<WasmFuncRef?> refs) {
|
||||
final oldUpdateableRefs = _updateableRefs;
|
||||
final oldSize = oldUpdateableRefs.length;
|
||||
final newUpdateableRefs = WasmArray<WasmArray<WasmFuncRef?>>.filled(
|
||||
oldSize + 1,
|
||||
refs,
|
||||
);
|
||||
newUpdateableRefs.copy(0, oldUpdateableRefs, 0, oldSize);
|
||||
_updateableRefs = newUpdateableRefs;
|
||||
}
|
||||
|
||||
Set<String> _loadedLibraryUris = {};
|
||||
|
||||
@pragma('dyn-module:callable')
|
||||
void registerLibraryUris(List<String> uris) {
|
||||
for (final uri in uris) {
|
||||
if (!_loadedLibraryUris.add(uri)) {
|
||||
throw StateError(
|
||||
'Cannot define the same library twice in dynamic modules.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,35 @@
|
||||
|
||||
import 'dart:async';
|
||||
import "dart:_js_helper"
|
||||
show JS, JSAnyToExternRef, jsStringFromDartString, jsStringToDartString;
|
||||
show
|
||||
JS,
|
||||
JSAnyToExternRef,
|
||||
jsStringFromDartString,
|
||||
jsStringToDartString,
|
||||
jsUint8ArrayFromDartUint8List;
|
||||
import "dart:_js_types" show JSStringImpl;
|
||||
import 'dart:_string';
|
||||
import 'dart:js_interop'
|
||||
show
|
||||
ByteBufferToJSArrayBuffer,
|
||||
JSArray,
|
||||
JSFunction,
|
||||
JSFunctionUtilExtension,
|
||||
JSString,
|
||||
JSArrayToList,
|
||||
JSStringToString,
|
||||
JSPromise,
|
||||
JSPromiseToFuture,
|
||||
StringToJSString;
|
||||
import 'dart:_js_helper' show JSValue;
|
||||
import 'dart:_js_helper' show dartifyRaw, JSValue;
|
||||
import 'dart:_js_types';
|
||||
import 'dart:_wasm';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data' show Uint8List;
|
||||
|
||||
part "class_id.dart";
|
||||
part "deferred.dart";
|
||||
part "dynamic_module.dart";
|
||||
part "print_patch.dart";
|
||||
part "symbol_patch.dart";
|
||||
|
||||
@@ -200,9 +210,14 @@ external bool get checkBounds;
|
||||
/// evaluator, and its value depends on `--minify`.
|
||||
external bool get minify;
|
||||
|
||||
@patch
|
||||
Future<Object?> loadDynamicModule({Uri? uri, Uint8List? bytes}) =>
|
||||
throw 'Unsupported operation';
|
||||
/// Whether dynamic module support is enabled for this build.
|
||||
///
|
||||
/// Enables shortcuts in some runtime logic if it is known that no support is
|
||||
/// needed for dynamic modules.
|
||||
///
|
||||
/// Reads of this variable is evaluated before the TFA by the constant
|
||||
/// evaluator, and its value depends on `--dynamic-module-main`.
|
||||
external bool get hasDynamicModuleSupport;
|
||||
|
||||
/// Compiler intrinsic to push an element to a Wasm array in a class field or
|
||||
/// variable.
|
||||
|
||||
@@ -337,8 +337,10 @@ class _InterfaceType extends _Type {
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer s = StringBuffer();
|
||||
final int index = classId.toIntSigned();
|
||||
s.write(_typeNames?[index] ?? 'minified:Class$index');
|
||||
final int index = scopeClassId(classId).toIntSigned();
|
||||
s.write(
|
||||
_moduleRttForClassId(classId).typeNames?[index] ?? 'minified:Class$index',
|
||||
);
|
||||
if (typeArguments.isNotEmpty) {
|
||||
s.write("<");
|
||||
for (int i = 0; i < typeArguments.length; i++) {
|
||||
@@ -654,40 +656,81 @@ class _RecordType extends _Type {
|
||||
identical(names, other.names);
|
||||
}
|
||||
|
||||
/// Maps each class id representing a type to the offset of that type-checker
|
||||
/// row in [_typeRowDisplacementTable].
|
||||
external WasmArray<WasmI32> get _typeRowDisplacementOffsets;
|
||||
|
||||
/// Tells whether a class `Sub` is a subclass of another class `Base.
|
||||
///
|
||||
/// Used via
|
||||
/// ```
|
||||
/// baseOffset = _typeRowDisplacementOffsets[Base.classId]`
|
||||
/// index = baseOffset + Sub.classId
|
||||
/// value = _typeRowDisplacementTable[index]
|
||||
/// if (value == Base.classId) {
|
||||
/// // => Sub.classId is a subclass of Base.classId
|
||||
/// // => Can use `index` into `_typeRowDisplacementSubstTable[index]`
|
||||
/// }
|
||||
///```
|
||||
external WasmArray<WasmI32> get _typeRowDisplacementTable;
|
||||
|
||||
/// Holds the canonical type argument substitution index for matching table
|
||||
/// entries (see above).
|
||||
///
|
||||
/// If `index` matches in [_typeRowDisplacementTable] then the same index can be
|
||||
/// used in this array to find the type argument substitution array for
|
||||
/// translating type arguments from a base class to a direct/indirect class.
|
||||
external WasmArray<WasmArray<_Type>> get _typeRowDisplacementSubstTable;
|
||||
|
||||
/// The names of all classes (indexed by class id) or null (if `--minify` was
|
||||
/// used)
|
||||
external WasmArray<String>? get _typeNames;
|
||||
|
||||
/// The non-negative index into [_typeRowDisplacementSubstTable] that represents
|
||||
/// that no substitution is needed.
|
||||
/// The non-negative index into [_ModuleRtt.typeRowDisplacementSubstTable] that
|
||||
/// represents that no substitution is needed.
|
||||
external WasmI32 get _noSubstitutionIndex;
|
||||
|
||||
external _ModuleRtt get _mainModuleRtt;
|
||||
|
||||
WasmArray<_ModuleRtt> _rttInfoForModule = WasmArray.filled(1, _mainModuleRtt);
|
||||
|
||||
@pragma('wasm:entry-point')
|
||||
void _registerModuleRtt(int moduleId, _ModuleRtt moduleRtt) {
|
||||
if (moduleId >= _rttInfoForModule.length) {
|
||||
final oldArray = _rttInfoForModule;
|
||||
final newArray = WasmArray.filled(moduleId + 1, moduleRtt);
|
||||
newArray.copy(0, oldArray, 0, oldArray.length);
|
||||
_rttInfoForModule = newArray;
|
||||
}
|
||||
_rttInfoForModule[moduleId] = moduleRtt;
|
||||
}
|
||||
|
||||
_ModuleRtt _moduleRttForClassId(WasmI32 classId) {
|
||||
if (!hasDynamicModuleSupport) {
|
||||
assert(classId <= ClassID.maxClassId);
|
||||
return _mainModuleRtt;
|
||||
}
|
||||
return _rttInfoForModule[classIdToModuleId(classId)];
|
||||
}
|
||||
|
||||
class _ModuleRtt {
|
||||
/// Maps each class id representing a type to the offset of that type-checker
|
||||
/// row in [typeRowDisplacementTable].
|
||||
final WasmArray<WasmI32> typeRowDisplacementOffsets;
|
||||
|
||||
/// Tells whether a class `Sub` is a subclass of another class `Base.
|
||||
///
|
||||
/// Used via
|
||||
/// ```
|
||||
/// baseOffset = _typeRowDisplacementOffsets[Base.classId]`
|
||||
/// index = baseOffset + Sub.classId
|
||||
/// value = _typeRowDisplacementTable[index]
|
||||
/// if (value == Base.classId) {
|
||||
/// // => Sub.classId is a subclass of Base.classId
|
||||
/// // => Can use `index` into `typeRowDisplacementSubstTable[index]`
|
||||
/// }
|
||||
///```
|
||||
///
|
||||
/// Takes two class IDs of classes to be queried. For dynamic modules this will
|
||||
/// allow the compiler to scope the query to a specific module. The class IDs
|
||||
/// are ignored for all other compilations.
|
||||
final WasmArray<WasmI32> typeRowDisplacementTable;
|
||||
|
||||
/// Holds the canonical type argument substitution index for matching table
|
||||
/// entries (see above).
|
||||
///
|
||||
/// If `index` matches in [typeRowDisplacementTable] then the same index can be
|
||||
/// used in this array to find the type argument substitution array for
|
||||
/// translating type arguments from a base class to a direct/indirect class.
|
||||
///
|
||||
/// Takes two class IDs of classes to be queried. For dynamic modules this will
|
||||
/// allow the compiler to scope the query to a specific module. The class IDs
|
||||
/// are ignored for all other compilations.
|
||||
final WasmArray<WasmArray<_Type>> typeRowDisplacementSubstTable;
|
||||
|
||||
/// The names of all classes (indexed by class id) or null (if `--minify` was
|
||||
/// used)
|
||||
final WasmArray<String>? typeNames;
|
||||
|
||||
@pragma("wasm:entry-point")
|
||||
const _ModuleRtt(
|
||||
this.typeRowDisplacementOffsets,
|
||||
this.typeRowDisplacementTable,
|
||||
this.typeRowDisplacementSubstTable,
|
||||
this.typeNames,
|
||||
);
|
||||
}
|
||||
|
||||
/// Type parameter environment used while comparing function types.
|
||||
///
|
||||
/// In the case of nested function types, the environment refers to the
|
||||
@@ -973,6 +1016,9 @@ abstract class _TypeUniverse {
|
||||
tTypeArguments,
|
||||
tEnv,
|
||||
substitutionIndex,
|
||||
// Since these are already proved to be subclasses, sId and tId are in the
|
||||
// same module or sId's module contains info for both.
|
||||
_moduleRttForClassId(sId).typeRowDisplacementSubstTable,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -998,6 +1044,9 @@ abstract class _TypeUniverse {
|
||||
sTypeArguments,
|
||||
tTypeArgument0,
|
||||
substitutionIndex,
|
||||
// Since these are already proved to be subclasses, sId and tId are in the
|
||||
// same module or sId's module contains info for both.
|
||||
_moduleRttForClassId(sId).typeRowDisplacementSubstTable,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1020,6 +1069,9 @@ abstract class _TypeUniverse {
|
||||
tTypeArgument0,
|
||||
tTypeArgument1,
|
||||
substitutionIndex,
|
||||
// Since these are already proved to be subclasses, sId and tId are in the
|
||||
// same module or sId's module contains info for both.
|
||||
_moduleRttForClassId(sId).typeRowDisplacementSubstTable,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1046,6 +1098,9 @@ abstract class _TypeUniverse {
|
||||
tTypeArguments,
|
||||
null,
|
||||
substitutionIndex,
|
||||
// Since these are already proved to be subclasses, sId and tId are in the
|
||||
// same module or sId's module contains info for both.
|
||||
_moduleRttForClassId(sId).typeRowDisplacementSubstTable,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1053,6 +1108,7 @@ abstract class _TypeUniverse {
|
||||
WasmArray<_Type> sTypeArguments,
|
||||
_Type tTypeArgument0,
|
||||
WasmI32 substitutionIndex,
|
||||
WasmArray<WasmArray<_Type>> substTable,
|
||||
) {
|
||||
// Check individual type arguments without substitution (fast case).
|
||||
if (substitutionIndex == _noSubstitutionIndex) {
|
||||
@@ -1060,8 +1116,7 @@ abstract class _TypeUniverse {
|
||||
}
|
||||
|
||||
// Substitue each argument before performing the subtype check (slow case).
|
||||
final substitutions =
|
||||
_typeRowDisplacementSubstTable[substitutionIndex.toIntSigned()];
|
||||
final substitutions = substTable[substitutionIndex.toIntSigned()];
|
||||
assert(substitutions.length == 1);
|
||||
final sArgForClassT = substituteTypeArgument(
|
||||
substitutions[0],
|
||||
@@ -1076,6 +1131,7 @@ abstract class _TypeUniverse {
|
||||
_Type tTypeArgument0,
|
||||
_Type tTypeArgument1,
|
||||
WasmI32 substitutionIndex,
|
||||
WasmArray<WasmArray<_Type>> substTable,
|
||||
) {
|
||||
// Check individual type arguments without substitution (fast case).
|
||||
if (substitutionIndex == _noSubstitutionIndex) {
|
||||
@@ -1084,8 +1140,7 @@ abstract class _TypeUniverse {
|
||||
}
|
||||
|
||||
// Substitue each argument before performing the subtype check (slow case).
|
||||
final substitutions =
|
||||
_typeRowDisplacementSubstTable[substitutionIndex.toIntSigned()];
|
||||
final substitutions = substTable[substitutionIndex.toIntSigned()];
|
||||
assert(substitutions.length == 2);
|
||||
final sArg1ForClassT = substituteTypeArgument(
|
||||
substitutions[1],
|
||||
@@ -1107,6 +1162,7 @@ abstract class _TypeUniverse {
|
||||
WasmArray<_Type> tTypeArguments,
|
||||
_Environment? tEnv,
|
||||
WasmI32 substitutionIndex,
|
||||
WasmArray<WasmArray<_Type>> substTable,
|
||||
) {
|
||||
// Check individual type arguments without substitution (fast case).
|
||||
if (substitutionIndex == _noSubstitutionIndex) {
|
||||
@@ -1119,8 +1175,7 @@ abstract class _TypeUniverse {
|
||||
}
|
||||
|
||||
// Substitute each argument before performing the subtype check (slow case).
|
||||
final substitutions =
|
||||
_typeRowDisplacementSubstTable[substitutionIndex.toIntSigned()];
|
||||
final substitutions = substTable[substitutionIndex.toIntSigned()];
|
||||
assert(substitutions.length == tTypeArguments.length);
|
||||
for (int i = 0; i < tTypeArguments.length; i++) {
|
||||
final sArgForClassT = substituteTypeArgument(
|
||||
@@ -1155,8 +1210,17 @@ abstract class _TypeUniverse {
|
||||
|
||||
@pragma('wasm:prefer-inline')
|
||||
static WasmI32 _checkSubclassRelationshipViaTable(WasmI32 sId, WasmI32 tId) {
|
||||
final offset = _typeRowDisplacementOffsets;
|
||||
final table = _typeRowDisplacementTable;
|
||||
final sModuleId = classIdToModuleId(sId);
|
||||
final tModuleId = classIdToModuleId(tId);
|
||||
if (tModuleId != mainModuleId) {
|
||||
if (sModuleId != tModuleId) return -1.toWasmI32();
|
||||
}
|
||||
|
||||
final rtt = _rttInfoForModule[sModuleId];
|
||||
final offset = rtt.typeRowDisplacementOffsets;
|
||||
final table = rtt.typeRowDisplacementTable;
|
||||
sId = localizeClassId(sId);
|
||||
tId = localizeClassId(tId);
|
||||
|
||||
final WasmI32 index = offset[tId.toIntSigned()] + sId;
|
||||
if (index.geU(table.length.toWasmI32())) return (-1).toWasmI32();
|
||||
|
||||
Reference in New Issue
Block a user