[vm/aot/tfa] Static weak references to method tearoffs

TEST=pkg/vm/testcases/transformations/type_flow/transformer/weak.dart
TEST=language/vm/static_weak_reference_test
TEST=language/vm/static_weak_reference_error_test

Bug: b/269223463
Change-Id: I23c8229c39217aa1c3f9fb576d8eefa5ceb1d8ad
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283421
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov 2023-02-22 01:01:29 +00:00 committed by Commit Queue
parent 5175036306
commit 143e4e9dc8
19 changed files with 610 additions and 43 deletions

View file

@ -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<Null> 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<Null> 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<Null> 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<Null> 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<Null> 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<Null> 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<Null> codeWeakWithStrongDillLibrary =
messageWeakWithStrongDillLibrary;

View file

@ -67,6 +67,12 @@ export '../fasta/fasta_codes.dart'
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
messageFfiPackedAnnotationAlignment,
messageNonPositiveArrayDimensions,
messageWeakReferenceNotStatic,
messageWeakReferenceNotOneArgument,
messageWeakReferenceReturnTypeNotNullable,
messageWeakReferenceMismatchReturnAndArgumentTypes,
messageWeakReferenceTargetNotStaticTearoff,
messageWeakReferenceTargetHasParameters,
noLength,
templateCantHaveNamedParameters,
templateCantHaveOptionalParameters,

View file

@ -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

View file

@ -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."

View file

@ -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

View file

@ -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<String, String>? 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");
}

View file

@ -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<Library> libraries, CoreTypes coreTypes, ClassHierarchy hierarchy,
{required bool nullSafety, required bool productMode}) {
final transformer = _Lowering(coreTypes, hierarchy,
nullSafety: nullSafety, productMode: productMode);
List<Library> 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);
}

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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<TypeExpr?> {
final TypesBuilder _typesBuilder;
final NativeCodeOracle _nativeCodeOracle;
final GenericInterfacesInfo _genericInterfacesInfo;
final StaticWeakReferences _staticWeakReferences;
final ProtobufHandler? _protobufHandler;
final Map<TreeNode, Call> callSites = <TreeNode, Call>{};
@ -586,6 +589,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
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<TypeExpr?> {
@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;

View file

@ -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<Extension> _usedExtensions = new Set<Extension>();
final Set<Typedef> _usedTypedefs = new Set<Typedef>();
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));

View file

@ -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);
}

View file

@ -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>(T Function()? x) => x;
}
main(List<String> args) {
print(weakRef1(used1));
print(weakRef1(used2));
print(weakRef1(unused));
print(A());
print(B());
}

View file

@ -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<core::String> 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
}

View file

@ -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>(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

View file

@ -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

View file

@ -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>(T Function()? x);
@pragma('vm:weak-tearoff-reference')
external T Function()? weakReference12<T>(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<T>() => 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.
}

View file

@ -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();
}