Skip to content

Commit 61952d4

Browse files
natebiggsCommit Queue
authored andcommitted
[dart2wasm] Fix dynamic modules not generating code for dynamic module callable members if they are inherited by a concrete class.
Prior to this we were using the dispatch table to get the class IDs in order to register the callable references via `recordClassTargetUse`. However, the dispatch table only contains concrete classes. A callable member may exist on an abstract class with no concrete subclasses. In this case the dispatch table would not contain any classes and the code for the member would never get generated. This is problematic when the abstract class (or a subclass) is dynamic module extendable. Then the callable member could be inherited onto a subclass in the dynamic module. Also makes sure to mark members that are callable AND overrideable as invoked so that the updateable dispatch mechanism registers the member. Change-Id: Icc079f76ab300aa761a47c8fbe24b8d9583d0da7 Fixes: #60602 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423260 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 0e040b8 commit 61952d4

File tree

5 files changed

+154
-79
lines changed

5 files changed

+154
-79
lines changed

pkg/dart2wasm/lib/dynamic_module_kernel_metadata.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ class MainModuleMetadata {
221221
late final DispatchTable dispatchTable;
222222

223223
/// Contains each invoked reference that targets an updateable function.
224-
final Set<Reference> invokedReferences;
224+
final Set<Reference> invokedOverrideableReferences;
225225

226226
/// Maps invocation keys (either selector or builtin) to the implementation's
227227
/// index in the runtime table. Key includes whether the key was invoked
@@ -240,7 +240,7 @@ class MainModuleMetadata {
240240
this.classMetadata,
241241
this.callableReferenceIds,
242242
this.dispatchTable,
243-
this.invokedReferences,
243+
this.invokedOverrideableReferences,
244244
this.keyInvocationToIndex,
245245
this.dfsOrderClassIds,
246246
this.mainModuleTranslatorOptions,
@@ -250,7 +250,7 @@ class MainModuleMetadata {
250250
this.mainModuleTranslatorOptions, this.mainModuleEnvironment)
251251
: classMetadata = {},
252252
callableReferenceIds = {},
253-
invokedReferences = {},
253+
invokedOverrideableReferences = {},
254254
keyInvocationToIndex = {},
255255
dfsOrderClassIds = [];
256256

@@ -299,7 +299,7 @@ class MainModuleMetadata {
299299

300300
dispatchTable.serialize(sink);
301301

302-
sink.writeList(invokedReferences, sink.writeReference);
302+
sink.writeList(invokedOverrideableReferences, sink.writeReference);
303303

304304
sink.writeMap(keyInvocationToIndex, sink.writeInt, sink.writeInt);
305305

pkg/dart2wasm/lib/dynamic_modules.dart

Lines changed: 129 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -430,93 +430,114 @@ class DynamicModuleInfo {
430430
b.global_set(moduleIdGlobal);
431431
}
432432

433-
void _initializeCallableReferences() {
434-
void collectCallableReference(Reference reference) {
435-
final member = reference.asMember;
433+
bool _isClassDynamicModuleInstantiable(Class cls) {
434+
return cls.isDynamicModuleExtendable(translator.coreTypes) ||
435+
cls.constructors
436+
.any((e) => e.isDynamicModuleCallable(translator.coreTypes)) ||
437+
cls.procedures.any((e) =>
438+
e.isFactory && e.isDynamicModuleCallable(translator.coreTypes));
439+
}
436440

437-
if (member.isExternal) {
438-
final isGeneratedIntrinsic = member is Procedure &&
439-
MemberIntrinsic.fromProcedure(translator.coreTypes, member) != null;
440-
if (!isGeneratedIntrinsic) return;
441+
void _initializeCallableReferences() {
442+
for (final lib in translator.component.libraries) {
443+
for (final member in lib.members) {
444+
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
445+
_forEachMemberReference(member, _registerStaticCallableTarget);
441446
}
447+
}
442448

443-
if (!member.isInstanceMember) {
444-
// Generate static members immediately since they are unconditionally
445-
// callable.
446-
metadata.callableReferenceIds[reference] ??=
447-
metadata.callableReferenceIds.length;
448-
translator.functions.getFunction(reference);
449-
return;
450-
}
449+
for (final classInfo in translator.classesSupersFirst) {
450+
final cls = classInfo.cls;
451+
if (cls == null) continue;
451452

452-
final selector = translator.dispatchTable.selectorForTarget(reference);
453-
final targetRanges = selector
454-
.targets(unchecked: false)
455-
.targetRanges
456-
.followedBy(selector.targets(unchecked: true).targetRanges);
457-
// Record usage of the target on all classes that inherit the member. The
458-
// class must be allocated in order for the target to be live.
459-
for (final (:range, :target) in targetRanges) {
460-
// Match any reference for the callable member, even if the reference
461-
// kind is not the same.
462-
if (target.asMember != member) continue;
463-
metadata.callableReferenceIds[target] ??=
464-
metadata.callableReferenceIds.length;
465-
for (int classId = range.start; classId <= range.end; ++classId) {
466-
translator.functions.recordClassTargetUse(classId, target);
453+
// Register any callable functions defined within this class.
454+
for (final member in cls.members) {
455+
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
456+
457+
if (!member.isInstanceMember) {
458+
// Generate static members immediately since they are unconditionally
459+
// callable.
460+
_forEachMemberReference(member, _registerStaticCallableTarget);
461+
continue;
467462
}
468-
}
469-
}
470463

471-
void collectCallableReferences(Member member) {
472-
if (member is Procedure) {
473-
collectCallableReference(member.reference);
474-
// We ignore the tear-off and let each dynamic module generate it for
475-
// itself.
476-
} else if (member is Field) {
477-
collectCallableReference(member.getterReference);
478-
if (member.hasSetter) {
479-
collectCallableReference(member.setterReference!);
464+
// Consider callable references invoked and therefore if they're
465+
// overrideable include them in the runtime dispatch table.
466+
if (member.isDynamicModuleOverrideable(translator.coreTypes)) {
467+
_forEachMemberReference(
468+
member, metadata.invokedOverrideableReferences.add);
480469
}
481-
} else if (member is Constructor &&
482-
// Skip types that don't extend Object in the wasm type hierarchy.
483-
// These types do not have directly invokable constructors.
484-
translator.classInfo[member.enclosingClass]!.struct
485-
.isSubtypeOf(translator.objectInfo.struct)) {
486-
collectCallableReference(member.reference);
487-
collectCallableReference(member.initializerReference);
488-
collectCallableReference(member.constructorBodyReference);
489470
}
490-
}
491471

492-
for (final lib in translator.component.libraries) {
493-
for (final member in lib.members) {
494-
if (member.isDynamicModuleCallable(translator.coreTypes)) {
495-
collectCallableReferences(member);
496-
}
472+
// Anonymous mixins' targets don't need to be registered since they aren't
473+
// directly allocatable.
474+
if (cls.isAnonymousMixin) continue;
475+
476+
if (cls.isAbstract && !_isClassDynamicModuleInstantiable(cls)) {
477+
continue;
497478
}
498479

499-
for (final cls in lib.classes) {
500-
for (final member in cls.members) {
501-
if (member.isDynamicModuleCallable(translator.coreTypes)) {
502-
collectCallableReferences(member);
503-
}
504-
}
480+
// For each dispatch target, register the member as callable from this
481+
// class.
482+
final targets = translator.hierarchy.getDispatchTargets(cls).followedBy(
483+
translator.hierarchy.getDispatchTargets(cls, setters: true));
484+
for (final member in targets) {
485+
if (!member.isDynamicModuleCallable(translator.coreTypes)) continue;
486+
487+
_forEachMemberReference(member,
488+
(reference) => _registerCallableDispatchTarget(reference, cls));
505489
}
506490
}
507491
}
508492

493+
/// If class [cls] is marked allocated then ensure we compile [target].
494+
///
495+
/// The [cls] may be marked allocated in
496+
/// [_initializeDynamicAllocatableClasses] which (together with this) will
497+
/// enqueue the [target] for compilation. Otherwise the [cls] must be
498+
/// allocated via a constructor call in the program itself.
499+
void _registerCallableDispatchTarget(Reference target, Class cls) {
500+
final member = target.asMember;
501+
502+
if (member.isExternal) {
503+
final isGeneratedIntrinsic = member is Procedure &&
504+
MemberIntrinsic.fromProcedure(translator.coreTypes, member) != null;
505+
if (!isGeneratedIntrinsic) return;
506+
}
507+
508+
final classId =
509+
(translator.classInfo[cls]!.classId as AbsoluteClassId).value;
510+
511+
metadata.callableReferenceIds[target] ??=
512+
metadata.callableReferenceIds.length;
513+
// The class must be allocated in order for the target to be live.
514+
translator.functions.recordClassTargetUse(classId, target);
515+
}
516+
517+
void _registerStaticCallableTarget(Reference target) {
518+
final member = target.asMember;
519+
520+
if (member.isExternal) {
521+
final isGeneratedIntrinsic = member is Procedure &&
522+
MemberIntrinsic.fromProcedure(translator.coreTypes, member) != null;
523+
if (!isGeneratedIntrinsic) return;
524+
}
525+
526+
// Generate static members immediately since they are unconditionally
527+
// callable.
528+
metadata.callableReferenceIds[target] ??=
529+
metadata.callableReferenceIds.length;
530+
translator.functions.getFunction(target);
531+
}
532+
509533
void _initializeDynamicAllocatableClasses() {
510-
for (final lib in translator.component.libraries) {
511-
for (final cls in lib.classes) {
512-
if (cls.isAnonymousMixin) continue;
513-
514-
if (cls.isDynamicModuleExtendable(translator.coreTypes) ||
515-
cls.constructors
516-
.any((e) => e.isDynamicModuleCallable(translator.coreTypes))) {
517-
translator.functions
518-
.recordClassAllocation(translator.classInfo[cls]!.classId);
519-
}
534+
for (final classInfo in translator.classesSupersFirst) {
535+
final cls = classInfo.cls;
536+
if (cls == null) continue;
537+
if (cls.isAnonymousMixin) continue;
538+
539+
if (_isClassDynamicModuleInstantiable(cls)) {
540+
translator.functions.recordClassAllocation(classInfo.classId);
520541
}
521542
}
522543
}
@@ -529,11 +550,12 @@ class DynamicModuleInfo {
529550
name: '#r_${builtin.name}');
530551
}
531552

532-
for (final reference in metadata.invokedReferences) {
553+
for (final reference in metadata.invokedOverrideableReferences) {
533554
final selector = translator.dispatchTable.selectorForTarget(reference);
534555
translator.functions.recordSelectorUse(selector, false);
535556

536-
final mainSelector = translator.dynamicMainModuleDispatchTable!
557+
final mainSelector = (translator.dynamicMainModuleDispatchTable ??
558+
translator.dispatchTable)
537559
.selectorForTarget(reference);
538560
final signature = _getGeneralizedSignature(mainSelector);
539561
final buildMain = buildSelectorBranch(reference, mainSelector);
@@ -547,6 +569,39 @@ class DynamicModuleInfo {
547569
}
548570
}
549571

572+
void _forEachMemberReference(Member member, void Function(Reference) f) {
573+
void passReference(Reference reference) {
574+
final checkedReference =
575+
translator.getFunctionEntry(reference, uncheckedEntry: false);
576+
f(checkedReference);
577+
578+
final uncheckedReference =
579+
translator.getFunctionEntry(reference, uncheckedEntry: true);
580+
if (uncheckedReference != checkedReference) {
581+
f(uncheckedReference);
582+
}
583+
}
584+
585+
if (member is Procedure) {
586+
passReference(member.reference);
587+
// We ignore the tear-off and let each dynamic module generate it for
588+
// itself.
589+
} else if (member is Field) {
590+
passReference(member.getterReference);
591+
if (member.hasSetter) {
592+
passReference(member.setterReference!);
593+
}
594+
} else if (member is Constructor &&
595+
// Skip types that don't extend Object in the wasm type hierarchy.
596+
// These types do not have directly invokable constructors.
597+
translator.classInfo[member.enclosingClass]!.struct
598+
.isSubtypeOf(translator.objectInfo.struct)) {
599+
passReference(member.reference);
600+
passReference(member.initializerReference);
601+
passReference(member.constructorBodyReference);
602+
}
603+
}
604+
550605
void finishDynamicModule() {
551606
_registerModuleRefs(
552607
isDynamicModule ? initFunction.body : translator.initFunction.body);
@@ -776,7 +831,7 @@ class DynamicModuleInfo {
776831
void callOverrideableDispatch(
777832
w.InstructionsBuilder b, SelectorInfo selector, Reference interfaceTarget,
778833
{required bool useUncheckedEntry}) {
779-
metadata.invokedReferences.add(interfaceTarget);
834+
metadata.invokedOverrideableReferences.add(interfaceTarget);
780835

781836
final localSignature = selector.signature;
782837
// If any input is not a RefType (i.e. it's an unboxed value) then wrap it

pkg/dynamic_modules/test/data/override_extra_params/dynamic_interface.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ can-be-overridden:
1515
- library: 'shared/shared.dart'
1616
class: 'Base'
1717
member: 'method3'
18+
- library: 'shared/shared.dart'
19+
class: 'Base'
20+
member: 'method4'
1821

1922
# TODO(sigmund): consider implying this for all extendable types.
2023
callable:
2124
- library: 'shared/shared.dart'
2225
class: 'Base'
2326
member: ''
27+
- library: 'shared/shared.dart'
28+
class: 'Base'
29+
member: 'method4'

pkg/dynamic_modules/test/data/override_extra_params/modules/entry1.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,17 @@ class Child extends Base {
1515
int method3<T>(int i, {int? j, T? a}) => i + (j ?? 0);
1616
}
1717

18+
class OtherChild extends Base {
19+
@override
20+
int method4<T>(int i, {int? j, T? a}) => i + (j ?? 0);
21+
}
22+
1823
@pragma('dyn-module:entry-point')
19-
Object? dynamicModuleEntrypoint() => Child();
24+
Object? dynamicModuleEntrypoint() {
25+
final c = Child();
26+
c.method1(2, 4);
27+
c.method2(2, j: 5, a: 'hello');
28+
c.method3(2, j: 5, a: null);
29+
c.method4(2, j: 5);
30+
return c;
31+
}

pkg/dynamic_modules/test/data/override_extra_params/shared/shared.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ abstract class Base {
88
int method2(int i, {int? j}) => i;
99

1010
int method3<T>(int i, {int? j}) => i;
11+
12+
int method4<T>(int i, {int? j}) => i;
1113
}

0 commit comments

Comments
 (0)