diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 4767e3839b1..a09765e1425 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -670,11 +670,13 @@ abstract class AstCodeGenerator List call(Reference target) { final targetModule = translator.moduleForReference(target); final isLocalModuleCall = targetModule == b.module; + final name = translator.functions.getFunctionName(target); if (isLocalModuleCall) { + b.comment('Direct call to $name'); return b.invoke(translator.directCallTarget(target)); } else { - b.comment('Indirect call to $target'); + b.comment('Direct call to $name (across modules)'); return translator.callReference(target, b); } } @@ -1669,7 +1671,7 @@ abstract class AstCodeGenerator translateExpression(node.receiver, signature.inputs.first), (w.FunctionType signature, ParameterInfo paramInfo) { _visitArguments(node.arguments, signature, paramInfo, 1); - }); + }, useUncheckedEntry: useUncheckedEntry); } @override @@ -1808,11 +1810,13 @@ abstract class AstCodeGenerator } } + final useUncheckedEntry = + translator.canUseUncheckedEntry(node.left, node); if (singleTarget != null) { left(); right(); call(translator.getFunctionEntry(singleTarget.reference, - uncheckedEntry: translator.canUseUncheckedEntry(node, node.left))); + uncheckedEntry: useUncheckedEntry)); } else { _virtualCall( node, @@ -1820,6 +1824,7 @@ abstract class AstCodeGenerator _VirtualCallKind.Call, left, right, + useUncheckedEntry: useUncheckedEntry, ); } if (leftNullable || rightNullable) { @@ -1853,33 +1858,34 @@ abstract class AstCodeGenerator _VirtualCallKind kind, void Function(w.FunctionType signature) pushReceiver, void Function(w.FunctionType signature, ParameterInfo) pushArguments, - {bool useUncheckedEntry = false}) { + {required bool useUncheckedEntry}) { assert(kind != _VirtualCallKind.Get || !useUncheckedEntry); SelectorInfo selector = translator.dispatchTable.selectorForTarget( interfaceTarget.referenceAs( getter: kind.isGetter, setter: kind.isSetter)); + final name = selector.entryPointName(useUncheckedEntry); assert(selector.name == interfaceTarget.name.text); pushReceiver(selector.signature); - if (selector.targetRanges.length == 1) { + final targets = selector.targets(unchecked: useUncheckedEntry); + + if (targets.targetRanges.length == 1) { // TODO(natebiggs): Ensure dynamic modules exclude this. - assert(selector.staticDispatchRanges.length == 1); - final target = translator.getFunctionEntry( - selector.targetRanges[0].target, - uncheckedEntry: useUncheckedEntry); + 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 (selector.targetRanges.isEmpty) { + if (targets.targetRanges.isEmpty) { // TODO(natebiggs): Ensure dynamic modules exclude this. // Unreachable call - b.comment("Virtual call of ${selector.name} with no targets" + 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) { @@ -1897,16 +1903,18 @@ abstract class AstCodeGenerator b.local_tee(receiverVar); pushArguments(selector.signature, selector.paramInfo); - if (selector.staticDispatchRanges.isNotEmpty) { + if (targets.staticDispatchRanges.isNotEmpty) { // TODO(natebiggs): Ensure dynamic modules exclude this. b.invoke(translator .getPolymorphicDispatchersForModule(b.module) - .getPolymorphicDispatcher(selector)); + .getPolymorphicDispatcher(selector, + useUncheckedEntry: useUncheckedEntry)); } else { - b.comment("Instance $kind of '${selector.name}'"); + b.comment("Instance $kind of '$name'"); b.local_get(receiverVar); - translator.callDispatchTable(b, selector); + translator.callDispatchTable(b, selector, + useUncheckedEntry: useUncheckedEntry); } return translator.outputOrVoid(selector.signature.outputs); @@ -2039,7 +2047,7 @@ abstract class AstCodeGenerator w.Label nullLabel = b.block(); translateExpression(node.receiver, translator.topInfo.nullableType); b.br_on_null(nullLabel); - }, (_, __) {}); + }, (_, __) {}, useUncheckedEntry: false); b.br(doneLabel); b.end(); // nullLabel switch (target.name.text) { @@ -2069,7 +2077,8 @@ abstract class AstCodeGenerator _VirtualCallKind.Get, (signature) => translateExpression(node.receiver, signature.inputs.first), - (_, __) {}); + (_, __) {}, + useUncheckedEntry: false); } } @@ -2181,7 +2190,7 @@ abstract class AstCodeGenerator b.br_on_null(nullLabel); translator.convertType( b, translator.topInfo.nullableType, signature.inputs[0]); - }, (_, __) {}); + }, (_, __) {}, useUncheckedEntry: false); b.br(doneLabel); b.end(); // nullLabel switch (target.name.text) { @@ -2212,7 +2221,8 @@ abstract class AstCodeGenerator _VirtualCallKind.Get, (signature) => translateExpression(node.receiver, signature.inputs.first), - (_, __) {}); + (_, __) {}, + useUncheckedEntry: false); } @override @@ -3187,13 +3197,13 @@ CodeGenerator? getInlinableMemberCodeGenerator(Translator translator, class SynchronousProcedureCodeGenerator extends AstCodeGenerator { final Procedure member; - final SynchronousProcedureKind kind; + final EntryPoint kind; SynchronousProcedureCodeGenerator(Translator translator, w.FunctionType functionType, this.member, this.kind) : super(translator, functionType, member) { - assert(!translator.needToCheckTypesFor(member) || - kind != SynchronousProcedureKind.normal); + assert( + !translator.needToCheckTypesFor(member) || kind != EntryPoint.normal); } @override @@ -3216,16 +3226,16 @@ class SynchronousProcedureCodeGenerator extends AstCodeGenerator { closures = translator.getClosures(member); switch (kind) { - case SynchronousProcedureKind.normal: + case EntryPoint.normal: b.comment('Normal Entry'); _makeNonMultiEntryPointFunction(); - case SynchronousProcedureKind.checked: + case EntryPoint.checked: b.comment('Checked Entry'); _makeMultipleEntryPoint(true); - case SynchronousProcedureKind.unchecked: + case EntryPoint.unchecked: b.comment('Unchecked Entry'); _makeMultipleEntryPoint(false); - case SynchronousProcedureKind.body: + case EntryPoint.body: b.comment('Body for Checked & Unchecked Entry'); _makeMultipleEntryPointSharedBody(); break; diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart index 751b6e0abbb..506c24452b2 100644 --- a/pkg/dart2wasm/lib/dispatch_table.dart +++ b/pkg/dart2wasm/lib/dispatch_table.dart @@ -39,21 +39,11 @@ class SelectorInfo { /// Is this an implicit or explicit setter? final bool isSetter; - /// Does this method have any tear-off uses? - bool hasTearOffUses = false; - - /// Does this method have any non-this uses? - bool hasNonThisUses = false; - - /// Targets for all concrete classes implementing this selector. + /// Whether we create multiple entry points for the selector. /// - /// As a subclass hierarchy often inherits the same target, we associate the - /// target with a range of class ids. The ranges are non-empty, - /// non-overlapping and sorted in ascending order. - late final List<({Range range, Reference target})> targetRanges; - late final Set targetSet = - targetRanges.map((e) => e.target).toSet(); - late final List<({Range range, Reference target})> staticDispatchRanges; + /// We create multiple entry points when any implementation of this selector + /// performs type checks on the passed arguments. + bool useMultipleEntryPoints = false; /// Wasm function type for the selector. /// @@ -63,12 +53,6 @@ class SelectorInfo { /// Number of concrete classes that provide this selector. late final int concreteClasses; - /// Offset of the selector in the dispatch table. - /// - /// For a class in [targetRanges], `class ID + offset` gives the offset of the - /// class member for this selector. - int? offset; - /// The selector's member's name. final String name; @@ -77,10 +61,30 @@ class SelectorInfo { /// `noSuchMethod` overrides to the dispatch table. final bool isNoSuchMethod; + late final SelectorTargets? _normal; + late final SelectorTargets? _checked; + late final SelectorTargets? _unchecked; + + SelectorTargets targets({required bool unchecked}) { + if (useMultipleEntryPoints) { + assert(_checked!.targetRanges.length == _unchecked!.targetRanges.length); + assert(_checked!.staticDispatchRanges.length == + _unchecked!.staticDispatchRanges.length); + return unchecked ? _unchecked! : _checked!; + } + assert(_checked == null && _unchecked == null); + return _normal!; + } + SelectorInfo._( this.translator, this.id, this.name, this.callCount, this.paramInfo, {required this.isSetter, required this.isNoSuchMethod}); + String entryPointName(bool unchecked) { + if (!useMultipleEntryPoints) return name; + return '$name (${unchecked ? 'unchecked' : 'checked'})'; + } + /// Compute the signature for the functions implementing members targeted by /// this selector. /// @@ -95,7 +99,7 @@ class SelectorInfo { List.generate(1 + paramInfo.paramCount, (_) => {}); List> outputSets = List.generate(returnCount, (_) => {}); List ensureBoxed = List.filled(1 + paramInfo.paramCount, false); - for (final (range: _, :target) in targetRanges) { + for (final (range: _, :target) in targets(unchecked: false).targetRanges) { Member member = target.asMember; DartType receiver = InterfaceType(member.enclosingClass!, Nullability.nonNullable); @@ -214,6 +218,39 @@ class SelectorInfo { } return w.RefType.def(heapTypes.single, nullable: nullable); } + + late final Set targetSet = useMultipleEntryPoints + ? {..._unchecked!._targetSet, ..._checked!._targetSet} + : _normal!._targetSet; +} + +class SelectorTargets { + /// Targets for all concrete classes implementing this selector. + /// + /// As a subclass hierarchy often inherits the same target, we associate the + /// target with a range of class ids. The ranges are non-empty, + /// non-overlapping and sorted in ascending order. + final List<({Range range, Reference target})> targetRanges; + + /// Targets that a interface call will check & directly call before falling + /// back to dispatch table calls. + /// + /// The targets in here are mainly the ones annotated with + /// `@pragma('wasm:static-dispatch')`. The compiler will generate then code + /// that first checks the receiver for those targets directly and issue direct + /// calls before falling back to dispatch table calls. + final List<({Range range, Reference target})> staticDispatchRanges; + + /// Offset of the selector in the dispatch table. + /// + /// For a class in [targetRanges], `class ID + offset` gives the offset of the + /// class member for this selector. + late final int offset; + + SelectorTargets(this.targetRanges, this.staticDispatchRanges); + + late final Set _targetSet = + targetRanges.map((e) => e.target).toSet(); } /// Builds the dispatch table for member calls. @@ -256,9 +293,6 @@ class DispatchTable { _importedWasmTables = WasmTableImporter(translator, 'dispatch'); SelectorInfo selectorForTarget(Reference target) { - // Dispatch table currently doesn't have unchecked entries. - assert(!target.isUncheckedEntryReference); - Member member = target.asMember; bool isGetter = target.isGetter || target.isTearOffReference; ProcedureAttributesMetadata metadata = @@ -296,8 +330,11 @@ class DispatchTable { isSetter: isSetter, isNoSuchMethod: member == translator.objectNoSuchMethod)); assert(selector.isSetter == isSetter); - selector.hasTearOffUses |= metadata.hasTearOffUses; - selector.hasNonThisUses |= metadata.hasNonThisUses; + final useMultipleEntryPoints = !member.isAbstract && + !target.isGetter && + !target.isTearOffReference && + translator.needToCheckTypesFor(member); + selector.useMultipleEntryPoints |= useMultipleEntryPoints; selector.paramInfo.merge(paramInfo); if (calledDynamically) { if (isGetter) { @@ -382,13 +419,11 @@ class DispatchTable { if (member is Field) { addMember(member.getterReference, staticDispatch); if (member.hasSetter) { - final target = translator.getFunctionEntry(member.setterReference!, - uncheckedEntry: false); + final target = member.setterReference!; addMember(target, staticDispatch); } } else if (member is Procedure) { - final target = translator.getFunctionEntry(member.reference, - uncheckedEntry: false); + final target = member.reference; addMember(target, staticDispatch); // `hasTearOffUses` can be true for operators as well, even though // it's not possible to tear-off an operator. (no syntax for it) @@ -449,14 +484,48 @@ class DispatchTable { : ranges .where((range) => staticDispatchPragmas.contains(range.target)) .toList(); - selector.targetRanges = ranges; - selector.staticDispatchRanges = staticDispatchRanges; + + 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) + ); + + 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)) { - selector.concreteClasses = 0; - selector.targetRanges = []; + // 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; + } } }); @@ -467,9 +536,11 @@ class DispatchTable { return true; } if (selector.callCount == 0) return false; - if (selector.targetRanges.length <= 1) return false; - if (selector.staticDispatchRanges.length == - selector.targetRanges.length) { + + final targets = selector.targets(unchecked: false); + + if (targets.targetRanges.length <= 1) return false; + if (targets.staticDispatchRanges.length == targets.targetRanges.length) { return false; } return true; @@ -494,19 +565,36 @@ class DispatchTable { final rows = >[]; for (final selector in selectors) { - final rowValues = <({int index, Reference value})>[]; - for (final (:range, :target) in selector.targetRanges) { - for (int classId = range.start; classId <= range.end; ++classId) { - rowValues.add((index: classId, value: target)); + Row 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)); + } } + 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)); } - rowValues.sort((a, b) => a.index.compareTo(b.index)); - rows.add(Row(rowValues)); } _table = buildRowDisplacementTable(rows); - for (int i = 0; i < rows.length; ++i) { - selectors[i].offset = rows[i].offset; + + 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; + } } _definedWasmTable = diff --git a/pkg/dart2wasm/lib/dynamic_forwarders.dart b/pkg/dart2wasm/lib/dynamic_forwarders.dart index 199ebcd62c9..c1419084320 100644 --- a/pkg/dart2wasm/lib/dynamic_forwarders.dart +++ b/pkg/dart2wasm/lib/dynamic_forwarders.dart @@ -99,8 +99,10 @@ class Forwarder { final selectors = translator.dispatchTable.dynamicGetterSelectors(memberName); final ranges = selectors - .expand((selector) => - selector.targetRanges.map((r) => (range: r.range, value: r.target))) + .expand((selector) => selector + .targets(unchecked: false) + .targetRanges + .map((r) => (range: r.range, value: r.target))) .toList(); ranges.sort((a, b) => a.range.start.compareTo(b.range.start)); @@ -145,8 +147,10 @@ class Forwarder { final selectors = translator.dispatchTable.dynamicSetterSelectors(memberName); final ranges = selectors - .expand((selector) => - selector.targetRanges.map((r) => (range: r.range, value: r.target))) + .expand((selector) => selector + .targets(unchecked: false) + .targetRanges + .map((r) => (range: r.range, value: r.target))) .toList(); ranges.sort((a, b) => a.range.start.compareTo(b.range.start)); @@ -197,7 +201,8 @@ class Forwarder { for (final selector in methodSelectors) { // Accumulates all class ID ranges that have the same target. final Map> targets = {}; - for (final (:range, :target) in selector.targetRanges) { + for (final (:range, :target) + in selector.targets(unchecked: false).targetRanges) { targets.putIfAbsent(target, () => []).add(range); } @@ -477,7 +482,8 @@ class Forwarder { translator.dispatchTable.dynamicGetterSelectors(memberName); final getterValueLocal = b.addLocal(translator.topInfo.nullableType); for (final selector in getterSelectors) { - for (final (:range, :target) in selector.targetRanges) { + for (final (:range, :target) + in selector.targets(unchecked: false).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 @@ -734,7 +740,7 @@ void generateNoSuchMethodCall( ) { final SelectorInfo noSuchMethodSelector = translator.dispatchTable .selectorForTarget(translator.objectNoSuchMethod.reference); - translator.functions.recordSelectorUse(noSuchMethodSelector); + translator.functions.recordSelectorUse(noSuchMethodSelector, false); final noSuchMethodParamInfo = noSuchMethodSelector.paramInfo; final noSuchMethodWasmFunctionType = noSuchMethodSelector.signature; @@ -773,7 +779,8 @@ void generateNoSuchMethodCall( // Get class id for virtual call pushReceiver(); - translator.callDispatchTable(b, noSuchMethodSelector); + translator.callDispatchTable(b, noSuchMethodSelector, + useUncheckedEntry: false); } class ClassIdRange { diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index 2ee953b2dc3..e0065a49549 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -26,6 +26,7 @@ class FunctionCollector { final Map _exports = {}; // Selector IDs that are invoked via GDT. final Set _calledSelectors = {}; + final Set _calledUncheckedSelectors = {}; // Class IDs for classes that are allocated somewhere in the program final Set _allocatedClasses = {}; // For each class ID, which functions should be added to the compilation queue @@ -231,9 +232,12 @@ class FunctionCollector { } } - void recordSelectorUse(SelectorInfo selector) { - if (_calledSelectors.add(selector.id)) { - for (final (:range, :target) in selector.targetRanges) { + void recordSelectorUse(SelectorInfo selector, bool useUncheckedEntry) { + final set = + useUncheckedEntry ? _calledUncheckedSelectors : _calledSelectors; + if (set.add(selector.id)) { + for (final (:range, :target) + in selector.targets(unchecked: useUncheckedEntry).targetRanges) { for (int classId = range.start; classId <= range.end; ++classId) { if (_allocatedClasses.contains(classId)) { // Class declaring or inheriting member is allocated somewhere. diff --git a/pkg/dart2wasm/lib/reference_extensions.dart b/pkg/dart2wasm/lib/reference_extensions.dart index ea5d222aeaf..7fee964aa1d 100644 --- a/pkg/dart2wasm/lib/reference_extensions.dart +++ b/pkg/dart2wasm/lib/reference_extensions.dart @@ -101,17 +101,17 @@ extension IsCustomReference on Reference { bool get isConstructorBodyReference => _constructorBodyReference[asMember] == this; - SynchronousProcedureKind get entryKind { + EntryPoint get entryKind { if (isUncheckedEntryReference) { - return SynchronousProcedureKind.unchecked; + return EntryPoint.unchecked; } if (isCheckedEntryReference) { - return SynchronousProcedureKind.checked; + return EntryPoint.checked; } if (isBodyReference) { - return SynchronousProcedureKind.body; + return EntryPoint.body; } - return SynchronousProcedureKind.normal; + return EntryPoint.normal; } } @@ -148,7 +148,7 @@ bool _memberCanHaveMultipleEntryPoints(Member member) { return false; } -enum SynchronousProcedureKind { +enum EntryPoint { // A single procedure doing type argument checks, optional argument handling // and the body. normal, diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 816a8ff20e6..4eab4c052fc 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -491,16 +491,19 @@ class Translator with KernelNodes { return b.emitUnreachableIfNoResult(function.type.outputs); } - void callDispatchTable(w.InstructionsBuilder b, SelectorInfo selector) { + void callDispatchTable(w.InstructionsBuilder b, SelectorInfo selector, + {required bool useUncheckedEntry}) { + final offset = selector.targets(unchecked: useUncheckedEntry).offset; + // TODO(natebiggs): Handle dispatch to dynamic module overrideable members. b.struct_get(topInfo.struct, FieldIndex.classId); - if (selector.offset! != 0) { - b.i32_const(selector.offset!); + if (offset != 0) { + b.i32_const(offset); b.i32_add(); } b.call_indirect(selector.signature, dispatchTable.getWasmTable(b.module)); b.emitUnreachableIfNoResult(selector.signature.outputs); - functions.recordSelectorUse(selector); + functions.recordSelectorUse(selector, useUncheckedEntry); } Class classForType(DartType type) { @@ -1175,18 +1178,7 @@ class Translator with KernelNodes { } w.FunctionType signatureForDirectCall(Reference target) { - if (target.asMember.isInstanceMember) { - if (target.isBodyReference) { - return makeFunctionTypeForBody(this, target.asMember); - } - if (target.isUncheckedEntryReference) { - // The unchecked entries use the same signature as normal entries. - final member = target.asMember; - return signatureForDirectCall(member is Field - ? member.setterReference! - : (member as Procedure).reference); - } - + if (target.asMember.isInstanceMember && !target.isBodyReference) { final selector = dispatchTable.selectorForTarget(target); if (selector.targetSet.contains(target)) { return selector.signature; @@ -1197,14 +1189,6 @@ class Translator with KernelNodes { ParameterInfo paramInfoForDirectCall(Reference target) { if (target.asMember.isInstanceMember) { - if (target.isUncheckedEntryReference) { - // The unchecked entries use the same signature as normal entries. - final member = target.asMember; - return paramInfoForDirectCall(member is Field - ? member.setterReference! - : (member as Procedure).reference); - } - final selector = dispatchTable.selectorForTarget(target); if (selector.targetSet.contains(target)) { return selector.paramInfo; @@ -1467,6 +1451,13 @@ class Translator with KernelNodes { bool shouldInline(Reference target, w.FunctionType signature) { if (!options.inlining) return false; + // Unchecked entry point functions perform very little, mainly optional + // parameter handling and then call the real body function. + // + // By inlining them we can often avoid downcasts and sometimes boxing. The + // force inlining here seem to even lead to overall size decreases. + if (target.isUncheckedEntryReference) return true; + final member = target.asMember; if (getPragma(member, "wasm:never-inline", true) == true) { return false; @@ -2134,14 +2125,20 @@ class PolymorphicDispatchers { final Translator translator; final w.ModuleBuilder callingModule; final cache = {}; + final uncheckedCache = {}; PolymorphicDispatchers(this.translator, this.callingModule); - CallTarget getPolymorphicDispatcher(SelectorInfo selector) { - assert(selector.targetRanges.length > 1); - return cache.putIfAbsent(selector, () { + CallTarget getPolymorphicDispatcher(SelectorInfo selector, + {required bool useUncheckedEntry}) { + assert( + selector.targets(unchecked: useUncheckedEntry).targetRanges.length > 1); + return (useUncheckedEntry && selector.useMultipleEntryPoints + ? uncheckedCache + : cache) + .putIfAbsent(selector, () { return PolymorphicDispatcherCallTarget( - translator, selector, callingModule); + translator, selector, callingModule, useUncheckedEntry); }); } } @@ -2150,9 +2147,10 @@ class PolymorphicDispatcherCallTarget extends CallTarget { final Translator translator; final SelectorInfo selector; final w.ModuleBuilder callingModule; + final bool useUncheckedEntry; - PolymorphicDispatcherCallTarget( - this.translator, this.selector, this.callingModule) + PolymorphicDispatcherCallTarget(this.translator, this.selector, + this.callingModule, this.useUncheckedEntry) : super(selector.signature); @override @@ -2162,11 +2160,16 @@ class PolymorphicDispatcherCallTarget extends CallTarget { bool get supportsInlining => true; @override - bool get shouldInline => selector.staticDispatchRanges.length <= 2; + bool get shouldInline => + selector + .targets(unchecked: useUncheckedEntry) + .staticDispatchRanges + .length <= + 2; @override - CodeGenerator get inliningCodeGen => - PolymorphicDispatcherCodeGenerator(translator, selector); + CodeGenerator get inliningCodeGen => PolymorphicDispatcherCodeGenerator( + translator, selector, useUncheckedEntry); @override late final w.BaseFunction function = (() { @@ -2182,20 +2185,24 @@ class PolymorphicDispatcherCallTarget extends CallTarget { class PolymorphicDispatcherCodeGenerator implements CodeGenerator { final Translator translator; final SelectorInfo selector; + final bool useUncheckedEntry; - PolymorphicDispatcherCodeGenerator(this.translator, this.selector); + PolymorphicDispatcherCodeGenerator( + this.translator, this.selector, this.useUncheckedEntry); @override void generate(w.InstructionsBuilder b, List paramLocals, w.Label? returnLabel) { final signature = selector.signature; - final targetRanges = selector.staticDispatchRanges + final targets = selector.targets(unchecked: useUncheckedEntry); + + final targetRanges = targets.staticDispatchRanges .map((entry) => (range: entry.range, value: entry.target)) .toList(); final bool needFallback = - selector.targetRanges.length > selector.staticDispatchRanges.length; + targets.targetRanges.length > targets.staticDispatchRanges.length; void emitDirectCall(Reference target) { for (int i = 0; i < signature.inputs.length; ++i) { @@ -2209,7 +2216,8 @@ class PolymorphicDispatcherCodeGenerator implements CodeGenerator { b.local_get(paramLocals[i]); } b.local_get(paramLocals[0]); - translator.callDispatchTable(b, selector); + translator.callDispatchTable(b, selector, + useUncheckedEntry: useUncheckedEntry); } b.local_get(paramLocals[0]);