diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart index 1339d56289d..2547fc80815 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -12982,6 +12982,64 @@ const MessageCode messageVoidWithTypeArguments = const MessageCode( problemMessage: r"""Type 'void' can't have type arguments.""", correctionMessage: r"""Try removing the type arguments."""); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceMismatchReturnAndArgumentTypes = + messageWeakReferenceMismatchReturnAndArgumentTypes; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceMismatchReturnAndArgumentTypes = + const MessageCode("WeakReferenceMismatchReturnAndArgumentTypes", + problemMessage: + r"""Return and argument types of a weak reference should match."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceNotOneArgument = + messageWeakReferenceNotOneArgument; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceNotOneArgument = const MessageCode( + "WeakReferenceNotOneArgument", + problemMessage: + r"""Weak reference should take one required positional argument."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceNotStatic = messageWeakReferenceNotStatic; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceNotStatic = const MessageCode( + "WeakReferenceNotStatic", + problemMessage: + r"""Weak reference pragma can be used on a static method only."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceReturnTypeNotNullable = + messageWeakReferenceReturnTypeNotNullable; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceReturnTypeNotNullable = const MessageCode( + "WeakReferenceReturnTypeNotNullable", + problemMessage: r"""Return type of a weak reference should be nullable."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceTargetHasParameters = + messageWeakReferenceTargetHasParameters; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceTargetHasParameters = const MessageCode( + "WeakReferenceTargetHasParameters", + problemMessage: + r"""The target of weak reference should not take parameters."""); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeWeakReferenceTargetNotStaticTearoff = + messageWeakReferenceTargetNotStaticTearoff; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageWeakReferenceTargetNotStaticTearoff = const MessageCode( + "WeakReferenceTargetNotStaticTearoff", + problemMessage: + r"""The target of weak reference should be a tearoff of a static method."""); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Code codeWeakWithStrongDillLibrary = messageWeakWithStrongDillLibrary; diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart index 43a3bef3d90..6abdfea0a8d 100644 --- a/pkg/front_end/lib/src/api_unstable/vm.dart +++ b/pkg/front_end/lib/src/api_unstable/vm.dart @@ -67,6 +67,12 @@ export '../fasta/fasta_codes.dart' messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer, messageFfiPackedAnnotationAlignment, messageNonPositiveArrayDimensions, + messageWeakReferenceNotStatic, + messageWeakReferenceNotOneArgument, + messageWeakReferenceReturnTypeNotNullable, + messageWeakReferenceMismatchReturnAndArgumentTypes, + messageWeakReferenceTargetNotStaticTearoff, + messageWeakReferenceTargetHasParameters, noLength, templateCantHaveNamedParameters, templateCantHaveOptionalParameters, diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index 97b0993b79c..ad10b3a71c0 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -995,6 +995,18 @@ VariableCouldBeNullDueToWrite/example: Fail VariablePatternTypeMismatchInSwitchHeads/analyzerCode: Fail WeakWithStrongDillLibrary/analyzerCode: Fail WeakWithStrongDillLibrary/example: Fail +WeakReferenceNotStatic/analyzerCode: Fail +WeakReferenceNotStatic/example: Fail +WeakReferenceNotOneArgument/analyzerCode: Fail +WeakReferenceNotOneArgument/example: Fail +WeakReferenceReturnTypeNotNullable/analyzerCode: Fail +WeakReferenceReturnTypeNotNullable/example: Fail +WeakReferenceMismatchReturnAndArgumentTypes/analyzerCode: Fail +WeakReferenceMismatchReturnAndArgumentTypes/example: Fail +WeakReferenceTargetNotStaticTearoff/analyzerCode: Fail +WeakReferenceTargetNotStaticTearoff/example: Fail +WeakReferenceTargetHasParameters/analyzerCode: Fail +WeakReferenceTargetHasParameters/example: Fail WebLiteralCannotBeRepresentedExactly/analyzerCode: Fail WebLiteralCannotBeRepresentedExactly/example: Fail YieldAsIdentifier/example: Fail diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index fc3b808c2db..09fa265d1e3 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -6459,3 +6459,21 @@ PatternAssignmentNotLocalVariable: method(x) { [global] = x; } + +WeakReferenceNotStatic: + problemMessage: "Weak reference pragma can be used on a static method only." + +WeakReferenceNotOneArgument: + problemMessage: "Weak reference should take one required positional argument." + +WeakReferenceReturnTypeNotNullable: + problemMessage: "Return type of a weak reference should be nullable." + +WeakReferenceMismatchReturnAndArgumentTypes: + problemMessage: "Return and argument types of a weak reference should match." + +WeakReferenceTargetNotStaticTearoff: + problemMessage: "The target of weak reference should be a tearoff of a static method." + +WeakReferenceTargetHasParameters: + problemMessage: "The target of weak reference should not take parameters." diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index 778a117be55..190282d2ad1 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -79,6 +79,7 @@ part(s) patch(es) patterns placing +pragma preexisting pubspec.yaml r @@ -98,6 +99,7 @@ superinterface supermixin t team +tearoff this.namedconstructor this.x trusttypes diff --git a/pkg/vm/lib/target/vm.dart b/pkg/vm/lib/target/vm.dart index bb24e099936..3458c075cac 100644 --- a/pkg/vm/lib/target/vm.dart +++ b/pkg/vm/lib/target/vm.dart @@ -182,7 +182,8 @@ class VmTarget extends Target { } bool productMode = environmentDefines!["dart.vm.product"] == "true"; - lowering.transformLibraries(libraries, coreTypes, hierarchy, + lowering.transformLibraries( + libraries, coreTypes, hierarchy, this, diagnosticReporter, nullSafety: flags.soundNullSafety, productMode: productMode); logger?.call("Lowering transformations performed"); @@ -199,7 +200,7 @@ class VmTarget extends Target { Map? environmentDefines, {void Function(String msg)? logger}) { bool productMode = environmentDefines!["dart.vm.product"] == "true"; - lowering.transformProcedure(procedure, coreTypes, hierarchy, + lowering.transformProcedure(procedure, coreTypes, hierarchy, this, nullSafety: flags.soundNullSafety, productMode: productMode); logger?.call("Lowering transformations performed"); } diff --git a/pkg/vm/lib/transformations/lowering.dart b/pkg/vm/lib/transformations/lowering.dart index f86ff95b40c..629dfe6ce2b 100644 --- a/pkg/vm/lib/transformations/lowering.dart +++ b/pkg/vm/lib/transformations/lowering.dart @@ -5,6 +5,7 @@ import 'package:kernel/ast.dart'; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/core_types.dart' show CoreTypes; +import 'package:kernel/target/targets.dart' show DiagnosticReporter, Target; import 'package:kernel/type_environment.dart' show StaticTypeContext, TypeEnvironment; @@ -13,6 +14,8 @@ import 'package:vm/transformations/specializer/factory_specializer.dart'; import 'for_in_lowering.dart' show ForInLowering; import 'late_var_init_transformer.dart' show LateVarInitTransformer; import 'list_literals_lowering.dart' show ListLiteralsLowering; +import 'pragma.dart' show ConstantPragmaAnnotationParser; +import 'static_weak_references.dart' show StaticWeakReferences; import 'type_casts_optimizer.dart' as typeCastsOptimizer show transformAsExpression; @@ -22,17 +25,24 @@ import 'type_casts_optimizer.dart' as typeCastsOptimizer /// Each transformation is applied locally to AST nodes of certain types /// after transforming children nodes. void transformLibraries( - List libraries, CoreTypes coreTypes, ClassHierarchy hierarchy, - {required bool nullSafety, required bool productMode}) { - final transformer = _Lowering(coreTypes, hierarchy, - nullSafety: nullSafety, productMode: productMode); + List libraries, + CoreTypes coreTypes, + ClassHierarchy hierarchy, + Target target, + DiagnosticReporter diagnosticReporter, + {required bool nullSafety, + required bool productMode}) { + final transformer = _Lowering(coreTypes, hierarchy, target, + diagnosticReporter: diagnosticReporter, + nullSafety: nullSafety, + productMode: productMode); libraries.forEach(transformer.visitLibrary); } -void transformProcedure( - Procedure procedure, CoreTypes coreTypes, ClassHierarchy hierarchy, +void transformProcedure(Procedure procedure, CoreTypes coreTypes, + ClassHierarchy hierarchy, Target target, {required bool nullSafety, required bool productMode}) { - final transformer = _Lowering(coreTypes, hierarchy, + final transformer = _Lowering(coreTypes, hierarchy, target, nullSafety: nullSafety, productMode: productMode); procedure.accept(transformer); } @@ -44,24 +54,37 @@ class _Lowering extends Transformer { final FactorySpecializer factorySpecializer; final ListLiteralsLowering listLiteralsLowering; final ForInLowering forInLowering; + final StaticWeakReferences staticWeakReferences; + final DiagnosticReporter? diagnosticReporter; Member? _currentMember; FunctionNode? _currentFunctionNode; StaticTypeContext? _cachedStaticTypeContext; - _Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, - {required this.nullSafety, required bool productMode}) + _Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, Target target, + {this.diagnosticReporter, + required this.nullSafety, + required bool productMode}) : env = TypeEnvironment(coreTypes, hierarchy), lateVarInitTransformer = LateVarInitTransformer(), factorySpecializer = FactorySpecializer(coreTypes), listLiteralsLowering = ListLiteralsLowering(coreTypes), - forInLowering = ForInLowering(coreTypes, productMode: productMode); + forInLowering = ForInLowering(coreTypes, productMode: productMode), + staticWeakReferences = StaticWeakReferences( + ConstantPragmaAnnotationParser(coreTypes, target)); StaticTypeContext get _staticTypeContext => _cachedStaticTypeContext ??= StaticTypeContext(_currentMember!, env); @override defaultMember(Member node) { + final diagnosticReporter = this.diagnosticReporter; + if (diagnosticReporter != null && + staticWeakReferences.isWeakReferenceDeclaration(node)) { + staticWeakReferences.validateWeakReferenceDeclaration( + node, diagnosticReporter); + } + if (node is Procedure && node.isRedirectingFactory) { // Keep bodies of redirecting factories unchanged because // front-end expects them to have a certain shape. @@ -92,6 +115,11 @@ class _Lowering extends Transformer { @override visitStaticInvocation(StaticInvocation node) { node.transformChildren(this); + final diagnosticReporter = this.diagnosticReporter; + if (diagnosticReporter != null && + staticWeakReferences.isWeakReference(node)) { + staticWeakReferences.validateWeakReference(node, diagnosticReporter); + } return factorySpecializer.transformStaticInvocation(node); } diff --git a/pkg/vm/lib/transformations/pragma.dart b/pkg/vm/lib/transformations/pragma.dart index cb8ffffd9b5..b8e638c05d3 100644 --- a/pkg/vm/lib/transformations/pragma.dart +++ b/pkg/vm/lib/transformations/pragma.dart @@ -14,12 +14,15 @@ const kResultTypeUsesPassedTypeArguments = "result-type-uses-passed-type-arguments"; const kVmRecognizedPragmaName = "vm:recognized"; const kVmDisableUnboxedParametersPragmaName = "vm:disable-unboxed-parameters"; +const kVmWeakTearoffReference = "vm:weak-tearoff-reference"; // Pragmas recognized by dart2wasm const kWasmEntryPointPragmaName = "wasm:entry-point"; const kWasmExportPragmaName = "wasm:export"; -abstract class ParsedPragma {} +abstract class ParsedPragma { + const ParsedPragma(); +} enum PragmaEntryPointType { Default, GetterOnly, SetterOnly, CallOnly } @@ -27,32 +30,36 @@ enum PragmaRecognizedType { AsmIntrinsic, GraphIntrinsic, Other } class ParsedEntryPointPragma extends ParsedPragma { final PragmaEntryPointType type; - ParsedEntryPointPragma(this.type); + const ParsedEntryPointPragma(this.type); } class ParsedResultTypeByTypePragma extends ParsedPragma { final DartType type; final bool resultTypeUsesPassedTypeArguments; - ParsedResultTypeByTypePragma( + const ParsedResultTypeByTypePragma( this.type, this.resultTypeUsesPassedTypeArguments); } class ParsedResultTypeByPathPragma extends ParsedPragma { final String path; - ParsedResultTypeByPathPragma(this.path); + const ParsedResultTypeByPathPragma(this.path); } class ParsedNonNullableResultType extends ParsedPragma { - ParsedNonNullableResultType(); + const ParsedNonNullableResultType(); } class ParsedRecognized extends ParsedPragma { final PragmaRecognizedType type; - ParsedRecognized(this.type); + const ParsedRecognized(this.type); } class ParsedDisableUnboxedParameters extends ParsedPragma { - ParsedDisableUnboxedParameters(); + const ParsedDisableUnboxedParameters(); +} + +class ParsedWeakTearoffReference extends ParsedPragma { + const ParsedWeakTearoffReference(); } abstract class PragmaAnnotationParser { @@ -78,6 +85,8 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser { } else if (constant is UnevaluatedConstant) { throw 'Error: unevaluated constant $constant'; } + } else if (annotation is InvalidExpression) { + return null; } else { throw 'Error: non-constant annotation $annotation'; } @@ -118,25 +127,25 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser { "or 'get' or 'call' for procedures."; } } - return type != null ? new ParsedEntryPointPragma(type) : null; + return type != null ? ParsedEntryPointPragma(type) : null; case kVmExactResultTypePragmaName: if (options is TypeLiteralConstant) { - return new ParsedResultTypeByTypePragma(options.type, false); + return ParsedResultTypeByTypePragma(options.type, false); } else if (options is StringConstant) { - return new ParsedResultTypeByPathPragma(options.value); + return ParsedResultTypeByPathPragma(options.value); } else if (options is ListConstant && options.entries.length == 2 && options.entries[0] is TypeLiteralConstant && options.entries[1] is StringConstant && (options.entries[1] as StringConstant).value == kResultTypeUsesPassedTypeArguments) { - return new ParsedResultTypeByTypePragma( + return ParsedResultTypeByTypePragma( (options.entries[0] as TypeLiteralConstant).type, true); } throw "ERROR: Unsupported option to '$kVmExactResultTypePragmaName' " "pragma: $options"; case kVmNonNullableResultType: - return new ParsedNonNullableResultType(); + return const ParsedNonNullableResultType(); case kVmRecognizedPragmaName: PragmaRecognizedType? type; if (options is StringConstant) { @@ -152,9 +161,11 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser { throw "ERROR: Unsupported option to '$kVmRecognizedPragmaName' " "pragma: $options"; } - return new ParsedRecognized(type); + return ParsedRecognized(type); case kVmDisableUnboxedParametersPragmaName: - return new ParsedDisableUnboxedParameters(); + return const ParsedDisableUnboxedParameters(); + case kVmWeakTearoffReference: + return const ParsedWeakTearoffReference(); case kWasmEntryPointPragmaName: return ParsedEntryPointPragma(PragmaEntryPointType.Default); case kWasmExportPragmaName: diff --git a/pkg/vm/lib/transformations/static_weak_references.dart b/pkg/vm/lib/transformations/static_weak_references.dart new file mode 100644 index 00000000000..e6fdc26c048 --- /dev/null +++ b/pkg/vm/lib/transformations/static_weak_references.dart @@ -0,0 +1,120 @@ +// Copyright (c) 2023, 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. + +/// Handling of static weak references. + +import 'package:front_end/src/api_unstable/vm.dart' + show + messageWeakReferenceNotStatic, + messageWeakReferenceNotOneArgument, + messageWeakReferenceReturnTypeNotNullable, + messageWeakReferenceMismatchReturnAndArgumentTypes, + messageWeakReferenceTargetNotStaticTearoff, + messageWeakReferenceTargetHasParameters; +import 'package:kernel/ast.dart'; +import 'package:kernel/target/targets.dart' show DiagnosticReporter; +import 'package:vm/transformations/pragma.dart' + show ParsedWeakTearoffReference, PragmaAnnotationParser; + +/// Recognizes and validates static weak references. +class StaticWeakReferences { + final PragmaAnnotationParser _annotationParser; + + StaticWeakReferences(this._annotationParser); + + bool isWeakReference(StaticInvocation node) => + _isAnnotatedWithWeakReferencePragma(node.target); + + bool isWeakReferenceDeclaration(Member node) => + _isAnnotatedWithWeakReferencePragma(node); + + bool _isAnnotatedWithWeakReferencePragma(Member m) { + for (final annotation in m.annotations) { + if (_annotationParser.parsePragma(annotation) + is ParsedWeakTearoffReference) { + return true; + } + } + return false; + } + + void validateWeakReference( + StaticInvocation node, DiagnosticReporter diagnosticReporter) { + assert(isWeakReference(node)); + + final arguments = node.arguments; + if (arguments.positional.length != 1 || arguments.named.isNotEmpty) { + diagnosticReporter.report(messageWeakReferenceNotOneArgument, + node.fileOffset, 1, node.location?.file); + return; + } + final arg = arguments.positional.single; + if (arg is ConstantExpression) { + final constant = arg.constant; + if (constant is StaticTearOffConstant) { + final target = constant.target; + if (target.isStatic) { + final function = target.function; + if (function.positionalParameters.isNotEmpty || + function.namedParameters.isNotEmpty || + function.typeParameters.isNotEmpty) { + diagnosticReporter.report(messageWeakReferenceTargetHasParameters, + node.fileOffset, 1, node.location?.file); + } + return; + } + } + } + diagnosticReporter.report(messageWeakReferenceTargetNotStaticTearoff, + node.fileOffset, 1, node.location?.file); + } + + void validateWeakReferenceDeclaration( + Member node, DiagnosticReporter diagnosticReporter) { + assert(isWeakReferenceDeclaration(node)); + + if (node is! Procedure || + !node.isStatic || + node.kind != ProcedureKind.Method) { + diagnosticReporter.report(messageWeakReferenceNotStatic, node.fileOffset, + 1, node.location?.file); + return; + } + final function = node.function; + if (function.positionalParameters.length != 1 || + function.requiredParameterCount != 1 || + function.namedParameters.isNotEmpty) { + diagnosticReporter.report(messageWeakReferenceNotOneArgument, + node.fileOffset, 1, node.location?.file); + return; + } + final returnType = function.returnType; + if (returnType.nullability != Nullability.nullable) { + diagnosticReporter.report(messageWeakReferenceReturnTypeNotNullable, + node.fileOffset, 1, node.location?.file); + } + if (returnType != function.positionalParameters.single.type) { + diagnosticReporter.report( + messageWeakReferenceMismatchReturnAndArgumentTypes, + node.fileOffset, + 1, + node.location?.file); + } + } + + // Returns argument expression of the weak reference. + // Assumes weak reference is valid. + Expression getWeakReferenceArgument(StaticInvocation node) { + assert(isWeakReference(node)); + return node.arguments.positional.single; + } + + // Returns target method of the weak reference. + // Assumes weak reference is valid. + Procedure getWeakReferenceTarget(StaticInvocation node) { + final arg = getWeakReferenceArgument(node); + return ((arg as ConstantExpression).constant as StaticTearOffConstant) + .target; + } +} diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index c9905387616..d2c006102a2 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -14,6 +14,9 @@ import 'package:kernel/class_hierarchy.dart' show ClosedWorldClassHierarchy; import 'package:kernel/library_index.dart' show LibraryIndex; import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/type_environment.dart'; +import 'package:vm/transformations/pragma.dart'; +import 'package:vm/transformations/static_weak_references.dart' + show StaticWeakReferences; import 'calls.dart'; import 'native_code.dart'; @@ -22,7 +25,6 @@ import 'summary.dart'; import 'summary_collector.dart'; import 'types.dart'; import 'utils.dart'; -import '../pragma.dart'; // TODO(alexmarkov) // Unordered list of various improvements in type flow analysis, @@ -1613,6 +1615,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler { final ProtobufHandler? protobufHandler; late NativeCodeOracle nativeCodeOracle; late _ClassHierarchyCache hierarchyCache; + late StaticWeakReferences staticWeakReferences; late SummaryCollector summaryCollector; late _InvocationsCache _invocationsCache; late _WorkList workList; @@ -1641,6 +1644,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler { nativeCodeOracle = new NativeCodeOracle(libraryIndex, annotationMatcher); hierarchyCache = new _ClassHierarchyCache(this, _genericInterfacesInfo, environment, target.flags.soundNullSafety); + staticWeakReferences = StaticWeakReferences(annotationMatcher); summaryCollector = new SummaryCollector( target, environment, @@ -1649,6 +1653,7 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler { hierarchyCache, nativeCodeOracle, hierarchyCache, + staticWeakReferences, protobufHandler); _invocationsCache = new _InvocationsCache(this); workList = new _WorkList(this); diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart index e488c3a7c06..7de2f662cae 100644 --- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart +++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart @@ -15,6 +15,8 @@ import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/type_environment.dart' show StaticTypeContext, SubtypeCheckMode, TypeEnvironment; import 'package:kernel/type_algebra.dart' show Substitution; +import 'package:vm/transformations/static_weak_references.dart' + show StaticWeakReferences; import 'calls.dart'; import 'native_code.dart'; @@ -524,6 +526,7 @@ class SummaryCollector extends RecursiveResultVisitor { final TypesBuilder _typesBuilder; final NativeCodeOracle _nativeCodeOracle; final GenericInterfacesInfo _genericInterfacesInfo; + final StaticWeakReferences _staticWeakReferences; final ProtobufHandler? _protobufHandler; final Map callSites = {}; @@ -586,6 +589,7 @@ class SummaryCollector extends RecursiveResultVisitor { this._typesBuilder, this._nativeCodeOracle, this._genericInterfacesInfo, + this._staticWeakReferences, this._protobufHandler) { constantAllocationCollector = new ConstantAllocationCollector(this); _nullMethodsAndGetters.addAll(getSelectors( @@ -1913,6 +1917,11 @@ class SummaryCollector extends RecursiveResultVisitor { @override TypeExpr visitStaticInvocation(StaticInvocation node) { + if (_staticWeakReferences.isWeakReference(node)) { + // Do not visit this StaticInvocation and its arguments as + // they are weakly reachable. + return _staticType(node); + } final args = _visitArguments(null, node.arguments, passTypeArguments: node.target.isFactory); final target = node.target; diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart index 4ca36207301..5b5e5631169 100644 --- a/pkg/vm/lib/transformations/type_flow/transformer.dart +++ b/pkg/vm/lib/transformations/type_flow/transformer.dart @@ -15,15 +15,17 @@ import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/library_index.dart' show LibraryIndex; import 'package:kernel/target/targets.dart'; import 'package:kernel/type_environment.dart'; +import 'package:vm/metadata/direct_call.dart'; +import 'package:vm/metadata/inferred_type.dart'; +import 'package:vm/metadata/procedure_attributes.dart'; +import 'package:vm/metadata/table_selector.dart'; +import 'package:vm/metadata/unboxing_info.dart'; +import 'package:vm/metadata/unreachable.dart'; +import 'package:vm/transformations/devirtualization.dart' show Devirtualization; +import 'package:vm/transformations/pragma.dart'; +import 'package:vm/transformations/static_weak_references.dart' + show StaticWeakReferences; -import '../../metadata/direct_call.dart'; -import '../../metadata/inferred_type.dart'; -import '../../metadata/procedure_attributes.dart'; -import '../../metadata/table_selector.dart'; -import '../../metadata/unboxing_info.dart'; -import '../../metadata/unreachable.dart'; -import '../devirtualization.dart' show Devirtualization; -import '../pragma.dart'; import 'analysis.dart'; import 'calls.dart'; import 'finalizable_types.dart'; @@ -710,6 +712,7 @@ class TreeShaker { final Set _usedExtensions = new Set(); final Set _usedTypedefs = new Set(); final FinalizableTypes _finalizableTypes; + final StaticWeakReferences _staticWeakReferences; late final FieldMorpher fieldMorpher; late final _TreeShakerTypeVisitor typeVisitor; late final _TreeShakerConstantVisitor constantVisitor; @@ -722,8 +725,9 @@ class TreeShaker { CoreTypes coreTypes, ClassHierarchy hierarchy, { this.treeShakeWriteOnlyFields = true, - }) : _finalizableTypes = new FinalizableTypes( - coreTypes, typeFlowAnalysis.libraryIndex, hierarchy) { + }) : _finalizableTypes = new FinalizableTypes( + coreTypes, typeFlowAnalysis.libraryIndex, hierarchy), + _staticWeakReferences = typeFlowAnalysis.staticWeakReferences { fieldMorpher = new FieldMorpher(this); typeVisitor = new _TreeShakerTypeVisitor(this); constantVisitor = new _TreeShakerConstantVisitor(this, typeVisitor); @@ -1375,6 +1379,14 @@ class _TreeShakerPass1 extends RemovingTransformer { @override TreeNode visitStaticInvocation( StaticInvocation node, TreeNode? removalSentinel) { + if (shaker._staticWeakReferences.isWeakReference(node)) { + final target = shaker._staticWeakReferences.getWeakReferenceTarget(node); + if (shaker.isMemberBodyReachable(target)) { + return transform( + shaker._staticWeakReferences.getWeakReferenceArgument(node)); + } + return NullLiteral()..fileOffset = node.fileOffset; + } node.transformOrRemoveChildren(this); if (_isUnreachable(node)) { return _makeUnreachableCall(_flattenArguments(node.arguments)); 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 ffb0f0b64f4..55ebaf8d889 100644 --- a/pkg/vm/test/transformations/type_flow/summary_collector_test.dart +++ b/pkg/vm/test/transformations/type_flow/summary_collector_test.dart @@ -15,6 +15,8 @@ import 'package:test/test.dart'; import 'package:vm/target/vm.dart' show VmTarget; import 'package:vm/transformations/pragma.dart' show ConstantPragmaAnnotationParser; +import 'package:vm/transformations/static_weak_references.dart' + show StaticWeakReferences; import 'package:vm/transformations/type_flow/analysis.dart'; import 'package:vm/transformations/type_flow/calls.dart'; import 'package:vm/transformations/type_flow/native_code.dart'; @@ -77,16 +79,17 @@ class PrintSummaries extends RecursiveVisitor { PrintSummaries(Target target, TypeEnvironment environment, CoreTypes coreTypes, ClosedWorldClassHierarchy hierarchy) { - final typesBuilder = new FakeTypesBuilder(coreTypes); - _summaryCollector = new SummaryCollector( + final typesBuilder = FakeTypesBuilder(coreTypes); + final annotationParser = ConstantPragmaAnnotationParser(coreTypes, target); + _summaryCollector = SummaryCollector( target, environment, hierarchy, - new FakeEntryPointsListener(typesBuilder), + FakeEntryPointsListener(typesBuilder), typesBuilder, - new NativeCodeOracle(coreTypes.index, - new ConstantPragmaAnnotationParser(coreTypes, target)), - new GenericInterfacesInfoImpl(coreTypes, hierarchy), + NativeCodeOracle(coreTypes.index, annotationParser), + GenericInterfacesInfoImpl(coreTypes, hierarchy), + StaticWeakReferences(annotationParser), /*_protobufHandler=*/ null); } diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart b/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart new file mode 100644 index 00000000000..5a89942f646 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2023, 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. + +int used1() => 1; +String used2() => 2.toString(); + +int unused() => 42; + +class A { + int x = used1(); + Function y = used2; + String toString() => "x: $x, y: $y"; +} + +class B { + static String getAccessor() => 'B'; + B() { + print(B.getAccessor()); + register(Lib.weakRef2(C.getAccessor)); + } + C getC() => C(); +} + +class C { + static String getAccessor() => 'C'; + C() { + print(C.getAccessor()); + } +} + +void register(String Function()? getAccessor) { + if (getAccessor != null) { + print(getAccessor()); + } +} + +@pragma('vm:weak-tearoff-reference') +Function? weakRef1(Function? x) => x; + +class Lib { + @pragma('vm:weak-tearoff-reference') + static T Function()? weakRef2(T Function()? x) => x; +} + +main(List args) { + print(weakRef1(used1)); + print(weakRef1(used2)); + print(weakRef1(unused)); + print(A()); + print(B()); +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart.expect new file mode 100644 index 00000000000..18415badcd1 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/weak.dart.expect @@ -0,0 +1,42 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +class A extends core::Object { +[@vm.inferred-type.metadata=dart.core::_Smi (value: 1)] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] [@vm.unboxing-info.metadata=(i)->i] field core::int x; +[@vm.inferred-type.metadata=dart.core::_Closure] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] field core::Function y = #C1; + synthetic constructor •() → self::A + : self::A::x = [@vm.inferred-type.metadata=dart.core::_Smi (value: 1)] self::used1(), super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6] method toString() → core::String + return "x: ${[@vm.direct-call.metadata=#lib::A.x] this.{self::A::x}{core::int}}, y: ${[@vm.direct-call.metadata=#lib::A.y] this.{self::A::y}{core::Function}}"; +} +class B extends core::Object { + constructor •() → self::B + : super core::Object::•() { + core::print([@vm.inferred-type.metadata=dart.core::_OneByteString (value: "B")] self::B::getAccessor()); + self::register(null); + } + static method getAccessor() → core::String + return "B"; +} +[@vm.unboxing-info.metadata=()->i]static method used1() → core::int + return 1; +static method used2() → core::String + return [@vm.direct-call.metadata=dart.core::_Smi.toString] [@vm.inferred-type.metadata=dart.core::_OneByteString (skip check)] 2.{core::int::toString}(){() → core::String}; +static method register([@vm.inferred-type.metadata=dart.core::_Closure?] () →? core::String getAccessor) → void { + if(!(getAccessor == null)) { + core::print(getAccessor{() → core::String}(){() → core::String}); + } +} +static method main(core::List args) → dynamic { + core::print(#C2); + core::print(#C1); + core::print(null); + core::print(new self::A::•()); + core::print(new self::B::•()); +} +constants { + #C1 = static-tearoff self::used2 + #C2 = static-tearoff self::used1 +} diff --git a/runtime/docs/compiler/pragmas_recognized_by_compiler.md b/runtime/docs/compiler/pragmas_recognized_by_compiler.md index f7d06bb515b..adedfd591b2 100644 --- a/runtime/docs/compiler/pragmas_recognized_by_compiler.md +++ b/runtime/docs/compiler/pragmas_recognized_by_compiler.md @@ -33,6 +33,22 @@ is always respected. VM will keep trying to inline the function in new contexts, not giving up after encountered contexts where inlining wasn't effective. With this some compile time is traded for expectation that the function has signficant type specialization, resulting in highly efficient inlined results in contexts where arguments types are known to the compiler. Example of this is `_List.of` constructor in dart core library. +### Declaring a static weak reference intrinsic method + +```dart +@pragma('vm:weak-tearoff-reference') +T Function()? weakRef(T Function()? x) => x; +``` + +Declares a special static method `weakRef` which can be used to create weak references +to tearoffs of static methods. Weak reference declaration should be a static method taking +one positional required argument. Its return type should be nullable and should match +argument type. It should be either `external` or return its argument (for backwards compatibility). + +Compiler replaces `weakRef(foo)` expression with either `foo` if method `foo()` is used and retained during +tree shaking, or `null` if `foo()` is only used through weak references. +Target `foo` should be a constant tearoff of a static method without arguments. + ## Annotations for return types and field types The VM is not able to see across method calls (apart from inlining) and diff --git a/runtime/docs/pragmas.md b/runtime/docs/pragmas.md index 84aa4661446..08dccee2e23 100644 --- a/runtime/docs/pragmas.md +++ b/runtime/docs/pragmas.md @@ -14,6 +14,7 @@ These pragmas are part of the VM's API and are safe for use in external code. | `vm:invisible` | Allows to mark a function as invisible so it will not appear on stack traces. | | `vm:always-consider-inlining` | Marks a function which particularly benefits from inlining and specialization in context of the caller (for example, when concrete types of arguments are known). Inliner will not give up after one failed inlining attempt and will continue trying to inline this function. | | `vm:platform-const` | Marks a static getter or a static field with an initializer where the getter body or field initializer evaluates to a constant value if the target operating system is known. | +| `vm:weak-tearoff-reference` | [Declaring a static weak reference intrinsic method.](compiler/pragmas_recognized_by_compiler.md#declaring-a-static-weak-reference-intrinsic-method) | ## Unsafe pragmas for general use diff --git a/tests/language/vm/static_weak_reference_error_test.dart b/tests/language/vm/static_weak_reference_error_test.dart new file mode 100644 index 00000000000..360f56acd46 --- /dev/null +++ b/tests/language/vm/static_weak_reference_error_test.dart @@ -0,0 +1,124 @@ +// Copyright (c) 2023, 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 errors for incorrect usage of 'vm:weak-tearoff-reference' pragma. + +@pragma('vm:weak-tearoff-reference') +Function? validWeakRef(Function? x) => x; + +@pragma('vm:weak-tearoff-reference') +Function? weakRef1({Function? x}) => x; +// ^ +// [cfe] Weak reference should take one required positional argument. + +@pragma('vm:weak-tearoff-reference') +Function? weakRef2({required Function? x}) => x; +// ^ +// [cfe] Weak reference should take one required positional argument. + +@pragma('vm:weak-tearoff-reference') +Function? weakRef3([Function? x]) => x; +// ^ +// [cfe] Weak reference should take one required positional argument. + +@pragma('vm:weak-tearoff-reference') +Function? weakRef4(Function? x, int y) => x; +// ^ +// [cfe] Weak reference should take one required positional argument. + +@pragma('vm:weak-tearoff-reference') +Function? weakRef5(Function? x, {int? y}) => x; +// ^ +// [cfe] Weak reference should take one required positional argument. + +@pragma('vm:weak-tearoff-reference') +Function weakRef6(Function x) => x; +// ^ +// [cfe] Return type of a weak reference should be nullable. + +@pragma('vm:weak-tearoff-reference') +Function weakRef7(Function x) => x; +// ^ +// [cfe] Return type of a weak reference should be nullable. + +@pragma('vm:weak-tearoff-reference') +dynamic validWeakRef8(dynamic x) => x; + +@pragma('vm:weak-tearoff-reference') +Function? weakRef9(void Function() x) => x; +// ^ +// [cfe] Return and argument types of a weak reference should match. + +@pragma('vm:weak-tearoff-reference') +Function? weakRef10(Function x) => x; +// ^ +// [cfe] Return and argument types of a weak reference should match. + +class A { + @pragma('vm:weak-tearoff-reference') + external static T Function()? validWeakReference11(T Function()? x); + + @pragma('vm:weak-tearoff-reference') + external T Function()? weakReference12(T Function()? x); +// ^ +// [cfe] Weak reference pragma can be used on a static method only. +} + +class B { + static int validTarget() => 42; + + B(); + B.bar(); + factory B.baz() => B(); + int instanceMethod() => 42; + + static int arg1(int x) => 42; + static int arg2([int? x]) => 42; + static int arg3({int? x}) => 42; + static int arg4() => 42; +} + +void main() { + validWeakRef(B.validTarget); // OK + + validWeakRef(B.new); +//^ +// [cfe] The target of weak reference should be a tearoff of a static method. + + validWeakRef(B.bar); +//^ +// [cfe] The target of weak reference should be a tearoff of a static method. + + validWeakRef(B.baz); +//^ +// [cfe] The target of weak reference should be a tearoff of a static method. + + validWeakRef(B().instanceMethod); +//^ +// [cfe] The target of weak reference should be a tearoff of a static method. + + final x = B.validTarget; + validWeakRef(x); +//^ +// [cfe] The target of weak reference should be a tearoff of a static method. + + const y = B.validTarget; + validWeakRef(y); // OK + + validWeakRef(B.arg1); +//^ +// [cfe] The target of weak reference should not take parameters. + + validWeakRef(B.arg2); +//^ +// [cfe] The target of weak reference should not take parameters. + + validWeakRef(B.arg3); +//^ +// [cfe] The target of weak reference should not take parameters. + + validWeakRef(B.arg4); +//^ +// [cfe] The target of weak reference should not take parameters. +} diff --git a/tests/language/vm/static_weak_reference_test.dart b/tests/language/vm/static_weak_reference_test.dart new file mode 100644 index 00000000000..22fd3dfc251 --- /dev/null +++ b/tests/language/vm/static_weak_reference_test.dart @@ -0,0 +1,47 @@ +// Copyright (c) 2023, 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 'vm:weak-tearoff-reference' pragma. + +import 'dart:io' show Platform; + +import "package:expect/expect.dart"; + +@pragma('vm:weak-tearoff-reference') +Function? weakRef(Function? x) => x; + +int used1() => 10; +int used2() => 20; +int unused1() => 30; +int unused2() => 40; + +final bool isAot = Platform.executable.contains('dart_precompiled_runtime'); + +void test(int expectedResult, bool isUsed, Function? ref) { + print(ref); + if (isUsed || !isAot) { + Expect.isNotNull(ref); + Expect.equals(expectedResult, ref!()); + } else { + Expect.isNull(ref); + } +} + +class A { + int foo1() => used1() + 1; + Function foo2() => used2; + int bar1() => unused1() + 1; + Function bar2() => unused2; +} + +main() { + test(10, true, weakRef(used1)); + test(20, true, weakRef(used2)); + test(30, false, weakRef(unused1)); + test(40, false, weakRef(unused2)); + + A a = A(); + a.foo1(); + a.foo2(); +}