[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:
Nate Biggs
2025-02-11 13:59:46 -08:00
committed by Commit Queue
parent aec2f8cd89
commit a4a4ca8a41
72 changed files with 4367 additions and 796 deletions
+3
View File
@@ -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);
}
});
+230 -53
View File
@@ -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.
+3 -1
View File
@@ -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
+141 -36
View File
@@ -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.
+98 -17
View File
@@ -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);
}
+11 -1
View File
@@ -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;
}
+88 -37
View File
@@ -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);
+86 -218
View File
@@ -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))
};
+322 -170
View File
@@ -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);
}
}
}
}
}
}
+19 -8
View File
@@ -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
+174 -18
View File
@@ -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);
+4 -1
View File
@@ -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);
+2 -1
View File
@@ -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);
+70 -51
View File
@@ -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();
+32 -7
View File
@@ -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;
},
};
+81
View File
@@ -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);
+170
View File
@@ -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;
}
+1 -3
View File
@@ -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,
+138
View File
@@ -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;
}
+1 -1
View File
@@ -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);
+172 -48
View File
@@ -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> {
+76 -42
View File
@@ -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) {
+1 -2
View File
@@ -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');
+4 -2
View File
@@ -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 {
+26 -21
View File
@@ -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;
}
+69
View File
@@ -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.',
);
}
}
}
+20 -5
View File
@@ -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.
+106 -42
View File
@@ -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();