diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart index 1f8fa1f833a..dbd169e8ceb 100644 --- a/pkg/dart2wasm/lib/target.dart +++ b/pkg/dart2wasm/lib/target.dart @@ -480,7 +480,8 @@ class WasmTarget extends Target { } @override - bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:"); + bool isSupportedPragma(String pragmaName) => + pragmaName.startsWith("wasm:") || pragmaName.startsWith("dyn-module:"); late final Map recordClasses; diff --git a/pkg/vm/lib/metadata/unboxing_info.dart b/pkg/vm/lib/metadata/unboxing_info.dart index 235891b4872..d447822866e 100644 --- a/pkg/vm/lib/metadata/unboxing_info.dart +++ b/pkg/vm/lib/metadata/unboxing_info.dart @@ -140,6 +140,12 @@ class UnboxingInfoMetadata { return false; } + void setFullyBoxed() { + argsInfo.length = 0; + returnInfo = UnboxingType.kBoxed; + mustUseStackCallingConvention = true; + } + // Returns `true` if this [UnboxingInfoMetadata] matches default one: // all arguments and the return value are boxed, the method is not // forced to use stack based calling convention and there is no override diff --git a/pkg/vm/lib/modular/target/vm.dart b/pkg/vm/lib/modular/target/vm.dart index e2addb91222..d4b5ef0d787 100644 --- a/pkg/vm/lib/modular/target/vm.dart +++ b/pkg/vm/lib/modular/target/vm.dart @@ -527,5 +527,6 @@ class VmTarget extends Target { : const CustomizedDartLibrarySupport(unsupported: {'mirrors'}); @override - bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("vm:"); + bool isSupportedPragma(String pragmaName) => + pragmaName.startsWith("vm:") || pragmaName.startsWith("dyn-module:"); } diff --git a/pkg/vm/lib/transformations/pragma.dart b/pkg/vm/lib/transformations/pragma.dart index 5930f6bd376..e89ed45556c 100644 --- a/pkg/vm/lib/transformations/pragma.dart +++ b/pkg/vm/lib/transformations/pragma.dart @@ -30,7 +30,14 @@ const kDynModuleEntryPointPragmaName = "dyn-module:entry-point"; abstract class ParsedPragma {} -enum PragmaEntryPointType { Default, GetterOnly, SetterOnly, CallOnly } +enum PragmaEntryPointType { + Default, + Extendable, + CanBeOverridden, + GetterOnly, + SetterOnly, + CallOnly +} enum PragmaRecognizedType { AsmIntrinsic, GraphIntrinsic, Other } @@ -72,6 +79,10 @@ class ParsedPlatformConstPragma implements ParsedPragma { const ParsedPlatformConstPragma(); } +class ParsedDynModuleEntryPointPragma implements ParsedPragma { + const ParsedDynModuleEntryPointPragma(); +} + abstract class PragmaAnnotationParser { /// May return 'null' if the annotation does not represent a recognized /// @pragma. @@ -177,20 +188,29 @@ class ConstantPragmaAnnotationParser implements PragmaAnnotationParser { case kVmDisableUnboxedParametersPragmaName: return const ParsedDisableUnboxedParameters(); case kVmKeepNamePragmaName: - return ParsedKeepNamePragma(); + return const ParsedKeepNamePragma(); case kVmPlatformConstPragmaName: - return ParsedPlatformConstPragma(); + return const ParsedPlatformConstPragma(); case kVmPlatformConstIfPragmaName: if (options is! BoolConstant) { throw "ERROR: Non-boolean option to '$kVmPlatformConstIfPragmaName' " "pragma: $options"; } - return options.value ? ParsedPlatformConstPragma() : null; + return options.value ? const ParsedPlatformConstPragma() : null; case kWasmEntryPointPragmaName: - return ParsedEntryPointPragma(PragmaEntryPointType.Default); + return const ParsedEntryPointPragma(PragmaEntryPointType.Default); case kWasmExportPragmaName: // Exports are treated as entry points. - return ParsedEntryPointPragma(PragmaEntryPointType.Default); + return const ParsedEntryPointPragma(PragmaEntryPointType.Default); + case kDynModuleExtendablePragmaName: + return const ParsedEntryPointPragma(PragmaEntryPointType.Extendable); + case kDynModuleCanBeOverriddenPragmaName: + return const ParsedEntryPointPragma( + PragmaEntryPointType.CanBeOverridden); + case kDynModuleCallablePragmaName: + return const ParsedEntryPointPragma(PragmaEntryPointType.Default); + case kDynModuleEntryPointPragmaName: + return const ParsedDynModuleEntryPointPragma(); default: return null; } diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index a25e3c16b3a..f23ec893cc6 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -469,7 +469,7 @@ final class _DispatchableInvocation extends _Invocation { } /// Marker for noSuchMethod() invocation in the map of invocation targets. - static final Member kNoSuchMethodMarker = new Procedure( + static final Member noSuchMethodMarker = new Procedure( new Name('noSuchMethod&&'), ProcedureKind.Method, new FunctionNode(null), fileUri: dummyUri); @@ -486,6 +486,8 @@ final class _DispatchableInvocation extends _Invocation { // along with more accurate receiver types for each target. final targets = {}; final selector = this.selector; + Type result = emptyType; + bool hasUnknownTargets = false; if (selector is FunctionSelector) { if (!_collectTargetsForFunctionCall( args.receiver, targets, typeFlowAnalysis)) { @@ -494,19 +496,25 @@ final class _DispatchableInvocation extends _Invocation { return selector.staticResultType; } } else { - _collectTargetsForReceiverType(args.receiver, targets, typeFlowAnalysis); + if (!_collectTargetsForReceiverType( + args.receiver, targets, typeFlowAnalysis)) { + // Set of targets is not fully known at compilation time. + hasUnknownTargets = true; + _setPolymorphic(); + result = typeFlowAnalysis.hierarchyCache + .fromStaticType(selector.staticReturnType, true); + } } // Calculate result as a union of results of direct invocations // corresponding to each target. - Type result = emptyType; if (targets.isEmpty) { tracePrint("No targets..."); } else { if (targets.length == 1) { final target = targets.keys.single; - if (target != kNoSuchMethodMarker) { + if (!identical(target, noSuchMethodMarker) && !hasUnknownTargets) { _setMonomorphicTarget(target); } else { _setPolymorphic(); @@ -520,7 +528,7 @@ final class _DispatchableInvocation extends _Invocation { Type receiver = receiverTypeBuilder.toType(); Type type; - if (target == kNoSuchMethodMarker) { + if (identical(target, noSuchMethodMarker)) { // Non-dynamic call-sites must hit NSM-forwarders in Dart 2. assert(selector is DynamicSelector); type = _processNoSuchMethod(receiver, typeFlowAnalysis); @@ -582,7 +590,8 @@ final class _DispatchableInvocation extends _Invocation { return result; } - void _collectTargetsForReceiverType( + // Returns true if set of targets is known at compilation time. + bool _collectTargetsForReceiverType( Type receiver, Map targets, TypeFlowAnalysis typeFlowAnalysis) { @@ -608,7 +617,11 @@ final class _DispatchableInvocation extends _Invocation { } } + ConeType? dynamicallyExtendableReceiver; if (receiver is ConeType) { + if (receiver.cls.hasDynamicallyExtendableSubtypes) { + dynamicallyExtendableReceiver = receiver; + } // Specialization of type cone will add dependency of the current // invocation to the receiver class. A new allocated class discovered // in the receiver cone will invalidate this invocation. @@ -633,6 +646,13 @@ final class _DispatchableInvocation extends _Invocation { if (isNullableReceiver) { _collectTargetsForNull(targets, typeFlowAnalysis); } + + if (dynamicallyExtendableReceiver != null) { + return _collectTargetsForDynamicallyExtendableType( + dynamicallyExtendableReceiver, targets, typeFlowAnalysis); + } + + return true; } void _collectTargetsForNull(Map targets, @@ -672,14 +692,14 @@ final class _DispatchableInvocation extends _Invocation { if (kPrintTrace) { tracePrint("Found non-trivial noSuchMethod for receiver $receiver"); } - _getReceiverTypeBuilder(targets, kNoSuchMethodMarker) + _getReceiverTypeBuilder(targets, noSuchMethodMarker) .addConcreteType(receiver); } else if (selector is DynamicSelector) { if (kPrintTrace) { tracePrint( "Dynamic selector - adding noSuchMethod for receiver $receiver"); } - _getReceiverTypeBuilder(targets, kNoSuchMethodMarker) + _getReceiverTypeBuilder(targets, noSuchMethodMarker) .addConcreteType(receiver); } else { if (kPrintTrace) { @@ -713,7 +733,7 @@ final class _DispatchableInvocation extends _Invocation { // Conservatively include noSuchMethod if selector is not from Object, // as class might miss the implementation. if (!dynamicTargetSet.isObjectMember) { - _getReceiverTypeBuilder(targets, kNoSuchMethodMarker).addType(receiver); + _getReceiverTypeBuilder(targets, noSuchMethodMarker).addType(receiver); } } @@ -736,6 +756,38 @@ final class _DispatchableInvocation extends _Invocation { return false; } + bool _collectTargetsForDynamicallyExtendableType( + ConeType receiver, + Map targets, + TypeFlowAnalysis typeFlowAnalysis) { + final cls = receiver.cls as _TFClassImpl; + // Collect possible targets among dynamically extendable + // subtypes as they may have allocated subtypes at run time. + final receiverTypeBuilder = _ReceiverTypeBuilder(); + receiverTypeBuilder.addType(receiver); + bool isDynamicallyOverridden = false; + for (final extendableSubtype in cls._dynamicallyExtendableSubtypes) { + Member? target = extendableSubtype.getDispatchTarget(selector); + if (target != null) { + if (areArgumentsValidFor(target)) { + // Overwrite previously added receiver type builder. + targets[target] = receiverTypeBuilder; + isDynamicallyOverridden = isDynamicallyOverridden || + typeFlowAnalysis.nativeCodeOracle + .isDynamicallyOverriddenMember(target); + } else { + assert(selector is DynamicSelector); + _recordMismatchedDynamicInvocation(target, typeFlowAnalysis); + } + } + } + if (selector is DynamicSelector) { + targets[noSuchMethodMarker] = receiverTypeBuilder; + isDynamicallyOverridden = true; + } + return isDynamicallyOverridden && !selector.name.isPrivate; + } + void _recordMismatchedDynamicInvocation( Member target, TypeFlowAnalysis typeFlowAnalysis) { // Although target is not going to be called because of @@ -1164,21 +1216,75 @@ class _DynamicTargetSet extends _DependencyTracker { class _TFClassImpl extends TFClass { final _TFClassImpl? superclass; final Set<_TFClassImpl> _allocatedSubtypes = new Set<_TFClassImpl>(); + final Set<_TFClassImpl> _dynamicallyExtendableSubtypes = + new Set<_TFClassImpl>(); late final Map _dispatchTargetsSetters = _initDispatchTargets(true); late final Map _dispatchTargetsNonSetters = _initDispatchTargets(false); final _DependencyTracker dependencyTracker = new _DependencyTracker(); - /// Flag indicating if this class has a noSuchMethod() method not inherited - /// from Object. - /// Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod(). - bool? hasNonTrivialNoSuchMethod; + // Flag indicating if this class has a noSuchMethod() method not inherited + // from Object. + // Lazy initialized by ClassHierarchyCache.hasNonTrivialNoSuchMethod(). + static const int flagHasNonTrivialNoSuchMethod = 1 << 0; + + // Flag indicating if flagHasNonTrivialNoSuchMethod was initialized. + static const int flagHasNonTrivialNoSuchMethodInitialized = 1 << 1; + + // This class can be extended by a dynamically loaded class + // (unknown at compilation time). + static const int flagIsDynamicallyExtendable = 1 << 2; + + // This class has a subtype which can be extended by a + // dynamically loaded class (unknown at compilation time). + static const int flagHasDynamicallyExtendableSubtypes = 1 << 3; + + int _flags = 0; _TFClassImpl(int id, Class classNode, this.superclass, Set supertypes, RecordShape? recordShape) : super(id, classNode, supertypes, recordShape); + bool get hasNonTrivialNoSuchMethodInitialized => + (_flags & flagHasNonTrivialNoSuchMethodInitialized) != 0; + + bool get hasNonTrivialNoSuchMethod => + (_flags & flagHasNonTrivialNoSuchMethod) != 0; + + set hasNonTrivialNoSuchMethod(bool value) { + if (value) { + _flags = _flags | + flagHasNonTrivialNoSuchMethod | + flagHasNonTrivialNoSuchMethodInitialized; + } else { + _flags = (_flags & ~flagHasNonTrivialNoSuchMethod) | + flagHasNonTrivialNoSuchMethodInitialized; + } + } + + bool get isDynamicallyExtendable => + (_flags & flagIsDynamicallyExtendable) != 0; + + set isDynamicallyExtendable(bool value) { + if (value) { + _flags |= flagIsDynamicallyExtendable; + } else { + _flags &= ~flagIsDynamicallyExtendable; + } + } + + bool get hasDynamicallyExtendableSubtypes => + (_flags & flagHasDynamicallyExtendableSubtypes) != 0; + + set hasDynamicallyExtendableSubtypes(bool value) { + if (value) { + _flags |= flagHasDynamicallyExtendableSubtypes; + } else { + _flags &= ~flagHasDynamicallyExtendableSubtypes; + } + } + Type? _specializedConeType; Type get specializedConeType => _specializedConeType ??= _calculateConeTypeSpecialization(); @@ -1431,12 +1537,21 @@ class _ClassHierarchyCache extends TypeHierarchy { return cls._dispatchTargetsNonSetters[Name(name)] as Field; } + void addDynamicallyExtendableClass(_TFClassImpl cls) { + cls.isDynamicallyExtendable = true; + for (final supertype in cls.supertypes) { + final supertypeImpl = supertype as _TFClassImpl; + supertypeImpl.hasDynamicallyExtendableSubtypes = true; + supertypeImpl._dynamicallyExtendableSubtypes.add(cls); + } + } + void seal() { _sealed = true; } @override - Type specializeTypeCone(TFClass baseClass, {bool allowWideCone = false}) { + Type specializeTypeCone(TFClass baseClass, {required bool allowWideCone}) { if (kPrintTrace) { tracePrint("specializeTypeCone for $baseClass"); } @@ -1476,17 +1591,17 @@ class _ClassHierarchyCache extends TypeHierarchy { bool _hasWideCone(_TFClassImpl cls) => cls._allocatedSubtypes.length > - _typeFlowAnalysis.config.maxAllocatedTypesInSetSpecialization; + _typeFlowAnalysis.config.maxAllocatedTypesInSetSpecialization || + cls.hasDynamicallyExtendableSubtypes; bool hasNonTrivialNoSuchMethod(TFClass c) { final classImpl = c as _TFClassImpl; - bool? value = classImpl.hasNonTrivialNoSuchMethod; - if (value == null) { - classImpl.hasNonTrivialNoSuchMethod = value = - (classImpl._dispatchTargetsNonSetters[noSuchMethodName] != - objectNoSuchMethod); + if (classImpl.hasNonTrivialNoSuchMethodInitialized) { + return classImpl.hasNonTrivialNoSuchMethod; } - return value; + return classImpl.hasNonTrivialNoSuchMethod = + (classImpl._dispatchTargetsNonSetters[noSuchMethodName] != + objectNoSuchMethod); } _DynamicTargetSet getDynamicTargetSet(DynamicSelector selector) { @@ -2017,6 +2132,14 @@ class TypeFlowAnalysis return callMethod; } + @override + void addDynamicallyExtendableClass(Class c) { + if (kPrintDebug) { + debugPrint("ADD DYNAMICALLY EXTENDABLE CLASS: $c"); + } + hierarchyCache.addDynamicallyExtendableClass(hierarchyCache.getTFClass(c)); + } + /// ---- Implementation of [SharedVariableBuilder] interface. ---- @override diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart index a6d6fb03402..fe4f1baad44 100644 --- a/pkg/vm/lib/transformations/type_flow/native_code.dart +++ b/pkg/vm/lib/transformations/type_flow/native_code.dart @@ -42,6 +42,10 @@ abstract class EntryPointsListener { /// Artificial call method corresponding to the given [closure]. Procedure getClosureCallMethod(Closure closure); + + /// Add class which can be extended by a dynamically loaded class + /// (unknown at compilation time). + void addDynamicallyExtendableClass(Class c); } class PragmaEntryPointsVisitor extends RecursiveVisitor { @@ -65,14 +69,18 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor { visitClass(Class klass) { final type = _annotationsDefineRoot(klass.annotations); if (type != null) { - if (type != PragmaEntryPointType.Default) { + if (type == PragmaEntryPointType.Default) { + if (!klass.isAbstract) { + entryPoints.addAllocatedClass(klass); + } + nativeCodeOracle.addClassReferencedFromNativeCode(klass); + } else if (type == PragmaEntryPointType.Extendable) { + entryPoints.addDynamicallyExtendableClass(klass); + nativeCodeOracle.addClassReferencedFromNativeCode(klass); + } else { throw "Error: pragma entry-point definition on a class must evaluate " "to null, true or false. See entry_points_pragma.md."; } - if (!klass.isAbstract) { - entryPoints.addAllocatedClass(klass); - } - nativeCodeOracle.addClassReferencedFromNativeCode(klass); } klass.visitChildren(this); } @@ -116,6 +124,12 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor { if (!proc.isSetter && !proc.isGetter && !proc.isFactory) { addSelector(CallKind.PropertyGet); } + break; + case PragmaEntryPointType.Extendable: + throw "Error: only class can be extendable"; + case PragmaEntryPointType.CanBeOverridden: + nativeCodeOracle.addDynamicallyOverriddenMember(proc); + break; } nativeCodeOracle.setMemberReferencedFromNativeCode(proc); @@ -168,6 +182,11 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor { case PragmaEntryPointType.CallOnly: throw "Error: can't generate invocation dispatcher for field $field" "through @pragma('vm:entry-point')"; + case PragmaEntryPointType.Extendable: + throw "Error: only class can be extendable"; + case PragmaEntryPointType.CanBeOverridden: + nativeCodeOracle.addDynamicallyOverriddenMember(field); + break; } nativeCodeOracle.setMemberReferencedFromNativeCode(field); @@ -178,6 +197,7 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor { class NativeCodeOracle { final LibraryIndex _libraryIndex; final Set _membersReferencedFromNativeCode = new Set(); + final Set _dynamicallyOverriddenMembers = new Set(); final Set _classesReferencedFromNativeCode = new Set(); final PragmaAnnotationParser _matcher; @@ -197,6 +217,13 @@ class NativeCodeOracle { bool isMemberReferencedFromNativeCode(Member member) => _membersReferencedFromNativeCode.contains(member); + void addDynamicallyOverriddenMember(Member member) { + _dynamicallyOverriddenMembers.add(member); + } + + bool isDynamicallyOverriddenMember(Member member) => + _dynamicallyOverriddenMembers.contains(member); + PragmaRecognizedType? recognizedType(Member member) { for (var annotation in member.annotations) { ParsedPragma? pragma = _matcher.parsePragma(annotation); diff --git a/pkg/vm/lib/transformations/type_flow/rta.dart b/pkg/vm/lib/transformations/type_flow/rta.dart index a8a6de86c62..0627aa22e87 100644 --- a/pkg/vm/lib/transformations/type_flow/rta.dart +++ b/pkg/vm/lib/transformations/type_flow/rta.dart @@ -558,6 +558,9 @@ class _EntryPointsListenerImpl implements EntryPointsListener { @override ConcreteType addAllocatedClass(Class c) => rta.addAllocatedClass(c); + @override + void addDynamicallyExtendableClass(Class c) {} + @override Field getRecordPositionalField(RecordShape shape, int pos) => throw 'Unsupported operation'; diff --git a/pkg/vm/lib/transformations/type_flow/types.dart b/pkg/vm/lib/transformations/type_flow/types.dart index 23557266f20..9a329c3bc83 100644 --- a/pkg/vm/lib/transformations/type_flow/types.dart +++ b/pkg/vm/lib/transformations/type_flow/types.dart @@ -75,6 +75,8 @@ class TFClass { return result; } + bool get hasDynamicallyExtendableSubtypes => false; + @override int get hashCode => id; @@ -290,7 +292,7 @@ abstract class TypeHierarchy extends TypesBuilder /// It is correct (although less accurate) for [specializeTypeCone] to return /// a larger set. In such case analysis would admit that a larger set of /// values can flow through the program. - Type specializeTypeCone(TFClass base, {bool allowWideCone = false}); + Type specializeTypeCone(TFClass base, {required bool allowWideCone}); /// Returns true if [cls] has allocated subtypes. bool hasAllocatedSubtypes(TFClass cls); @@ -870,6 +872,7 @@ class ConeType extends Type { @override bool hasEmptySpecialization(TypeHierarchy typeHierarchy) => + !cls.hasDynamicallyExtendableSubtypes && !typeHierarchy.hasAllocatedSubtypes(cls); @override @@ -927,7 +930,8 @@ class ConeType extends Type { } } -/// Type representing a subtype cone which has too many concrete classes. +/// Type representing a subtype cone which has too many concrete classes +/// or may contain dynamically loaded subtypes (unknown at compilation time). /// It contains instances of all Dart types which extend, mix-in or implement /// certain class. class WideConeType extends ConeType { diff --git a/pkg/vm/lib/transformations/type_flow/unboxing_info.dart b/pkg/vm/lib/transformations/type_flow/unboxing_info.dart index c1feb30ebab..d51953ef2b0 100644 --- a/pkg/vm/lib/transformations/type_flow/unboxing_info.dart +++ b/pkg/vm/lib/transformations/type_flow/unboxing_info.dart @@ -127,14 +127,17 @@ class UnboxingInfoManager { void _updateUnboxingInfoOfMember(Member member, TypeFlowAnalysis typeFlowAnalysis, UnboxingInfoMetadata unboxingInfo) { + if (_nativeCodeOracle.isDynamicallyOverriddenMember(member)) { + unboxingInfo.setFullyBoxed(); + return; + } + if (!typeFlowAnalysis.isMemberUsed(member)) { return; } if (_cannotUnbox(member)) { - unboxingInfo.argsInfo.length = 0; - unboxingInfo.returnInfo = UnboxingType.kBoxed; - unboxingInfo.mustUseStackCallingConvention = true; + unboxingInfo.setFullyBoxed(); return; } diff --git a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart index e495b8e9a07..e0fc9c831f5 100644 --- a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart +++ b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart @@ -74,6 +74,9 @@ class FakeEntryPointsListener implements EntryPointsListener { @override Procedure getClosureCallMethod(Closure closure) => closure.createCallMethod(); + + @override + void addDynamicallyExtendableClass(Class c) {} } class FakeSharedVariable implements SharedVariable { diff --git a/pkg/vm/test/transformations/type_flow/types_test.dart b/pkg/vm/test/transformations/type_flow/types_test.dart index 36b990621cd..3a9afb22028 100644 --- a/pkg/vm/test/transformations/type_flow/types_test.dart +++ b/pkg/vm/test/transformations/type_flow/types_test.dart @@ -22,13 +22,13 @@ class TestTypeHierarchy extends TypeHierarchy { : super(coreTypes, target); @override - Type specializeTypeCone(TFClass base, {bool allowWideCone = false}) { + Type specializeTypeCone(TFClass base, {required bool allowWideCone}) { return specializations[base.classNode]!; } @override bool hasAllocatedSubtypes(TFClass cls) => - specializeTypeCone(cls) is! EmptyType; + specializeTypeCone(cls, allowWideCone: true) is! EmptyType; @override TFClass getTFClass(Class c) => diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart b/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart new file mode 100644 index 00000000000..02e032856ef --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart @@ -0,0 +1,56 @@ +// Copyright (c) 2024, 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. + +// Test for @pragma('dyn-module:extendable'). + +abstract class A1 { + Object foo(); // Unboxed. +} + +class B1 implements A1 { + int foo() => 42; +} + +abstract class C1 implements A1 {} // Unused, eliminated. + +abstract class A2 { + @pragma('dyn-module:can-be-overridden') + Object foo(); // Not unboxed. +} + +class B2 implements A2 { + int foo() => 42; +} + +@pragma('dyn-module:extendable') +abstract class C2 implements A2 {} // Not eliminated. + +void call1(A1 obj) { + print(obj.foo()); // Devirtualized, constant result. +} + +void call2(A2 obj) { + print(obj.foo()); // Not devirtualized, no constant result. +} + +void cast1(A1 obj) { + print(obj is B1); // Eliminated. + print(obj as B1); // Eliminated. +} + +void cast2(A2 obj) { + print(obj is B2); // Not eliminated. + print(obj as B2); // Not eliminated. +} + +List opaque = [] + ..add(B1()) + ..add(B2()); + +main() { + call1(opaque[0]); + call2(opaque[1]); + cast1(opaque[0]); + cast2(opaque[1]); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart.expect new file mode 100644 index 00000000000..4f2386bbc82 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart.expect @@ -0,0 +1,86 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "dart:_internal" as _in; + +abstract class A1 extends core::Object { + + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] + [@vm.unboxing-info.metadata=()->i] + abstract method foo() → core::Object; +} +class B1 extends core::Object implements self::A1 { + synthetic constructor •() → self::B1 + : super core::Object::•() + ; + + [@vm.inferred-return-type.metadata=dart.core::_Smi (value: 42)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] + [@vm.unboxing-info.metadata=()->i] + method foo() → core::int + return 42; +} +abstract class A2 extends core::Object { + + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] + [@vm.unboxing-info.metadata=[!regcc]] + @#C3 + abstract method foo() → core::Object; +} +class B2 extends core::Object implements self::A2 { + synthetic constructor •() → self::B2 + : super core::Object::•() + ; + + [@vm.inferred-return-type.metadata=dart.core::_Smi (value: 42)] + [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] + [@vm.unboxing-info.metadata=[!regcc]] + method foo() → core::int + return 42; +} +@#C5 +abstract class C2 extends core::Object implements self::A2 { +} + +[@vm.inferred-type.metadata=dart.core::_GrowableList] +static field core::List opaque = let final core::List #t1 = [@vm.inferred-type.metadata=dart.core::_GrowableList] core::_GrowableList::•(0) in block { + [@vm.call-site-attributes.metadata=receiverType:dart.core::List] [@vm.direct-call.metadata=dart.core::_GrowableList.add] [@vm.inferred-type.metadata=!? (skip check)] #t1.{core::List::add}(new self::B1::•()){(dynamic) → void}; + [@vm.call-site-attributes.metadata=receiverType:dart.core::List] [@vm.direct-call.metadata=dart.core::_GrowableList.add] [@vm.inferred-type.metadata=!? (skip check)] #t1.{core::List::add}(new self::B2::•()){(dynamic) → void}; +} =>#t1; + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method call1([@vm.inferred-arg-type.metadata=#lib::B1] self::A1 obj) → void { + core::print([@vm.direct-call.metadata=#lib::B1.foo] [@vm.inferred-type.metadata=dart.core::_Smi (skip check) (value: 42)] obj.{self::A1::foo}(){() → core::Object}); +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method call2([@vm.inferred-arg-type.metadata=!] self::A2 obj) → void { + core::print([@vm.inferred-type.metadata=!] obj.{self::A2::foo}(){() → core::Object}); +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method cast1([@vm.inferred-arg-type.metadata=#lib::B1] self::A1 obj) → void { + core::print(true); + core::print(_in::unsafeCast(obj)); +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method cast2([@vm.inferred-arg-type.metadata=!] self::A2 obj) → void { + core::print(obj is self::B2); + core::print(obj as self::B2); +} + +[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)] +static method main() → dynamic { + self::call1([@vm.direct-call.metadata=dart.core::_GrowableList.[]] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=dart.core::_GrowableList] self::opaque.{core::List::[]}(0){(core::int) → dynamic} as{TypeError,ForDynamic} self::A1); + self::call2([@vm.direct-call.metadata=dart.core::_GrowableList.[]] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=dart.core::_GrowableList] self::opaque.{core::List::[]}(1){(core::int) → dynamic} as{TypeError,ForDynamic} self::A2); + self::cast1([@vm.direct-call.metadata=dart.core::_GrowableList.[]] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=dart.core::_GrowableList] self::opaque.{core::List::[]}(0){(core::int) → dynamic} as{TypeError,ForDynamic} self::A1); + self::cast2([@vm.direct-call.metadata=dart.core::_GrowableList.[]] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=dart.core::_GrowableList] self::opaque.{core::List::[]}(1){(core::int) → dynamic} as{TypeError,ForDynamic} self::A2); +} +constants { + #C1 = "dyn-module:can-be-overridden" + #C2 = null + #C3 = core::pragma {name:#C1, options:#C2} + #C4 = "dyn-module:extendable" + #C5 = core::pragma {name:#C4, options:#C2} +}