Skip to content

Commit

Permalink
[vm,dart2wasm,tfa] Support dynamic interface in TFA
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
alexmarkov authored and Commit Queue committed Jun 10, 2024
1 parent 05d9e5b commit 9a3ca8f
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 41 deletions.
3 changes: 2 additions & 1 deletion pkg/dart2wasm/lib/target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 6 additions & 0 deletions pkg/vm/lib/metadata/unboxing_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pkg/vm/lib/modular/target/vm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:");
}
32 changes: 26 additions & 6 deletions pkg/vm/lib/transformations/pragma.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down
165 changes: 144 additions & 21 deletions pkg/vm/lib/transformations/type_flow/analysis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)) {
Expand All @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9a3ca8f

Please sign in to comment.