[vm,dart2wasm,tfa] Support dynamic interface in TFA
TEST=pkg/vm/testcases/transformations/type_flow/transformer/dynamic_module_extendable.dart Change-Id: I6ef43053d86b1a12259f1cb4d0b542c9519591f8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370143 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
05d9e5bf37
commit
9a3ca8ffd4
@@ -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<RecordShape, Class> recordClasses;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = <Member, _ReceiverTypeBuilder>{};
|
||||
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<Member, _ReceiverTypeBuilder> 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<Member, _ReceiverTypeBuilder> 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<Member, _ReceiverTypeBuilder> 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<Name, Member> _dispatchTargetsSetters =
|
||||
_initDispatchTargets(true);
|
||||
late final Map<Name, Member> _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<TFClass> 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
|
||||
|
||||
@@ -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<Member> _membersReferencedFromNativeCode = new Set<Member>();
|
||||
final Set<Member> _dynamicallyOverriddenMembers = new Set<Member>();
|
||||
final Set<Class> _classesReferencedFromNativeCode = new Set<Class>();
|
||||
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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ class FakeEntryPointsListener implements EntryPointsListener {
|
||||
|
||||
@override
|
||||
Procedure getClosureCallMethod(Closure closure) => closure.createCallMethod();
|
||||
|
||||
@override
|
||||
void addDynamicallyExtendableClass(Class c) {}
|
||||
}
|
||||
|
||||
class FakeSharedVariable implements SharedVariable {
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
+86
@@ -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<dynamic>]
|
||||
static field core::List<dynamic> opaque = let final core::List<dynamic> #t1 = [@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::•<dynamic>(0) in block {
|
||||
[@vm.call-site-attributes.metadata=receiverType:dart.core::List<dynamic>] [@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<dynamic>] [@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<self::B1>(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<dynamic>] 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<dynamic>] 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<dynamic>] 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<dynamic>] 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}
|
||||
}
|
||||
Reference in New Issue
Block a user