[record_use] Stop recording annotations

Stop recording annotations (in the VM and dart2wasm).

The visitor will stop visiting as soon as it is in an expression that
is in the annotations list.

The tests have been migrated so that their constants occur in the executable code instead of annotations.

One test has been kept to see that the annotations no longer are
recorded:
TEST=pkg/vm/testcases/transformations/record_use/instance_method.dart

A new test has been added to ensure we're also recording constants
nested inside other constants.
TEST=pkg/vm/testcases/transformations/record_use/nested_instance_constant.dart

Closes: https://github.com/dart-lang/native/issues/2977
Change-Id: I5d814c8500b510628025ab2daeb115e782dd423a
Cq-Include-Trybots: luci.dart.try:dart2wasm-asserts-linux-chrome-try,dart2wasm-asserts-minified-linux-d8-try,dart2wasm-linux-chrome-try,dart2wasm-linux-d8-try,dart2wasm-linux-firefox-try,dart2wasm-linux-jscm-chrome-try,dart2wasm-linux-optimized-jsc-try,pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try,dart2js-canary-linux-try,dart2js-hostasserts-linux-d8-try,dart2js-linux-chrome-try,dart2js-linux-firefox-try,dart2js-mac-chrome-try,dart2js-mac-safari-try,dart2js-minified-csp-linux-chrome-try,dart2js-minified-linux-d8-try,dart2js-unit-linux-x64-release-try,dart2js-win-chrome-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478440
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Daco Harkes
2026-02-05 01:13:52 -08:00
committed by Commit Queue
parent e68f866b10
commit 61babf9699
19 changed files with 191 additions and 127 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ vars = {
"i18n_rev": "dd8a792a8492370a594706c8304d2eb8db844d7a",
"leak_tracker_rev": "f5620600a5ce1c44f65ddaa02001e200b096e14c", # rolled manually
"material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e",
"native_rev": "2f33e1af716668ab79346fdded6fa3f05ffd827b", # rolled manually while record_use is experimental
"native_rev": "4c78a125651d3499b616b2b0624ea35f3315d20c", # rolled manually while record_use is experimental
"protobuf_rev": "9e30258e0aa6a6430ee36c84b75308a9702fde42",
"pub_rev": "ed2628f65de5d54922e2d34ebbcb1bafa8c93cc2", # rolled manually
"shelf_rev": "dd830a0338b31bee92fe7ebc20b9bb963403b6b0",
+3
View File
@@ -36,6 +36,9 @@ It will also include some additional static information, like the source
location of the call, or even which parameters where provided (if the parameters
are constant).
Only calls in reachable code (executable code) are tracked. Calls appearing
within metadata (annotations) are ignored.
### Example output
Example outputs can be found in [pkg/vm/testcases/transformations/record_use/](
@@ -132,6 +132,7 @@ const dart2jsNotSupported = {
'instance_duplicates.dart',
'instance_method.dart',
'instance_not_annotation.dart',
'nested_instance_constant.dart',
'nested.dart',
'record_enum.dart',
'record_instance_constant_empty.dart',
+5 -1
View File
@@ -4,7 +4,11 @@
/// Recognition and validation of usage recording annotations.
///
/// A static method to be recorded can be annotated with `@RecordUse()`.
/// A static method or class to be recorded can be annotated with
/// `@RecordUse()`.
///
/// Only usages in reachable code (executable code) are tracked. Usages
/// appearing within metadata (annotations) are ignored.
library;
import 'package:front_end/src/codes/diagnostic.dart' as diag;
+12 -7
View File
@@ -576,20 +576,25 @@ class Immutable {
const Immutable([this.reason = '']);
}
/// Annotates a static method to be recorded.
/// Annotates a static method or a class to be recorded.
///
/// Applies to static functions, top-level functions, or extension methods.
/// Applies to static functions, top-level functions, extension methods, or
/// classes with constant constructors.
///
/// During compilation, all statically resolved calls to an annotated function
/// are registered, and information about the annotated functions, the calls,
/// and their arguments, is then made available to post-compile steps.
// TODO(srawlins): Enforce with `TargetKind.method`.
/// or loadings of constant instances of an annotated class in reachable code
/// are registered. Information about these usages is then made available to
/// post-compile steps.
///
/// Only usages in reachable code (executable code) are tracked.
/// Usages appearing within metadata (annotations) are ignored.
// TODO(srawlins): Enforce with `TargetKind.method` or `TargetKind.classType`.
@experimental
class RecordUse {
/// Creates a [RecordUse] instance.
///
/// This annotation can be placed as an annotation on functions whose
/// statically resolved calls should be registered
/// This annotation can be placed as an annotation on functions or classes
/// whose usages in reachable code should be registered.
const RecordUse();
}
@@ -27,12 +27,16 @@ LoadingUnitLookup _getDefaultLoadingUnitLookup(ast.Component component) {
_loadingUnitForLibrary(enclosingLibrary(node)!, loadingUnits).toString();
}
/// Collect calls to methods annotated with `@RecordUse`.
/// Collect calls and constant instances annotated with `@RecordUse`.
///
/// Identify and collect all calls to static methods annotated in the given
/// [component]. This requires the deferred loading to be handled already to
/// also save which loading unit the call is made in. Write the result into a
/// JSON at [recordedUsagesFile].
/// Identify and collect all calls to static methods and loadings of constant
/// instances of classes annotated in reachable code in the given [component].
/// This requires the deferred loading to be handled already to also save which
/// loading unit the usage is made in. Write the result into a JSON at
/// [recordedUsagesFile].
///
/// Only usages in reachable code (executable code) are tracked.
/// Usages appearing within metadata (annotations) are ignored.
///
/// The purpose of this feature is to be able to pass the recorded information
/// to packages in a post-compilation step, allowing them to remove or modify
@@ -79,6 +83,8 @@ class _RecordUseVisitor extends ast.RecursiveVisitor {
@override
void visitStaticInvocation(ast.StaticInvocation node) {
if (_isAnnotation(node)) return;
staticCallRecorder.recordStaticInvocation(node);
super.visitStaticInvocation(node);
@@ -86,11 +92,32 @@ class _RecordUseVisitor extends ast.RecursiveVisitor {
@override
void visitConstantExpression(ast.ConstantExpression node) {
if (_isAnnotation(node)) return;
staticCallRecorder.recordConstantExpression(node);
instanceUseRecorder.recordConstantExpression(node);
super.visitConstantExpression(node);
}
@override
void defaultExpression(ast.Expression node) {
// Prune the traversal of annotations. Since we catch the outermost
// expression of an annotation here, we don't need to check sub-expressions
// recursively in [_isAnnotation].
if (_isAnnotation(node)) return;
super.defaultExpression(node);
}
/// Returns whether [node] is a top-level expression in an annotation list.
///
/// This only checks the immediate parent because [_RecordUseVisitor] relies on
/// [defaultExpression] catching annotations at the outermost expression level
/// and pruning the traversal into any sub-expressions.
static bool _isAnnotation(ast.TreeNode? node) {
final parent = node?.parent;
return parent is ast.Annotatable && parent.annotations.contains(node);
}
}
Recordings _usages(
@@ -5,12 +5,9 @@
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(A());
print(const MyClass(42));
}
@MyClass(42)
class A {}
@RecordUse()
class MyClass {
final int i;
@@ -1,17 +1,11 @@
library #lib;
import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "dart:core" as core;
import "package:meta/meta.dart" show RecordUse;
@#C2
class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
@#C3
@#C1
class MyClass extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 42)]
@@ -22,10 +16,10 @@ class MyClass extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
core::print(new self::A::•());
core::print(#C3);
}
constants {
#C1 = 42
#C2 = self::MyClass {i:#C1}
#C3 = meta::RecordUse {}
#C1 = meta::RecordUse {}
#C2 = 42
#C3 = self::MyClass {i:#C2}
}
@@ -5,21 +5,20 @@
import 'package:meta/meta.dart' show RecordUse;
void main() {
print(A());
print(
const MyClass(
i: 15,
s: 's',
b: true,
l: [
{'l': 3},
],
m: {'h': false},
n: null,
),
);
}
@MyClass(
i: 15,
s: 's',
b: true,
l: [
{'l': 3},
],
m: {'h': false},
n: null,
)
class A {}
@RecordUse()
class MyClass {
final int i;
@@ -1,17 +1,11 @@
library #lib;
import self as self;
import "dart:core" as core;
import "package:meta/meta.dart" as meta;
import "dart:core" as core;
import "package:meta/meta.dart" show RecordUse;
@#C12
class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
@#C13
@#C1
class MyClass extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 15)]
@@ -42,20 +36,20 @@ class MyClass extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
core::print(new self::A::•());
core::print(#C13);
}
constants {
#C1 = 15
#C2 = "s"
#C3 = "h"
#C4 = false
#C5 = <core::String, core::bool>{#C3:#C4}
#C6 = true
#C7 = "l"
#C8 = 3
#C9 = <core::String, core::int>{#C7:#C8}
#C10 = <core::Map<core::String, core::int>>[#C9]
#C11 = null
#C12 = self::MyClass {i:#C1, s:#C2, m:#C5, b:#C6, l:#C10, n:#C11}
#C13 = meta::RecordUse {}
#C1 = meta::RecordUse {}
#C2 = 15
#C3 = "s"
#C4 = "h"
#C5 = false
#C6 = <core::String, core::bool>{#C4:#C5}
#C7 = true
#C8 = "l"
#C9 = 3
#C10 = <core::String, core::int>{#C8:#C9}
#C11 = <core::Map<core::String, core::int>>[#C10]
#C12 = null
#C13 = self::MyClass {i:#C2, s:#C3, m:#C6, b:#C7, l:#C11, n:#C12}
}
@@ -1,41 +1,6 @@
{
"constants": [
{
"type": "int",
"value": 42
},
{
"type": "instance",
"value": {
"i": 0
}
}
],
"locations": [
{
"uri": "instance_method.dart"
}
],
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.4.0"
},
"recordings": [
{
"definition": {
"identifier": {
"name": "MyClass",
"uri": "instance_method.dart"
},
"loading_unit": "1"
},
"instances": [
{
"@": 0,
"constant_index": 1,
"loading_unit": "1"
}
]
}
]
}
}
@@ -0,0 +1,24 @@
// Copyright (c) 2026, 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.
import 'package:meta/meta.dart' show RecordUse;
void main() {
// Refers to a const Wrapper instance, which contains a Recorded instance.
// It should still be recorded.
print(_wrapper);
}
@RecordUse()
class Recorded {
final String id;
const Recorded(this.id);
}
class Wrapper {
final Recorded recorded;
const Wrapper(this.recorded);
}
const _wrapper = const Wrapper(Recorded('id'));
@@ -0,0 +1,31 @@
library #lib;
import self as self;
import "package:meta/meta.dart" as meta;
import "dart:core" as core;
import "package:meta/meta.dart" show RecordUse;
@#C1
class Recorded extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-type.metadata=dart.core::_OneByteString (value: "id")]
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
final field core::String id;
}
class Wrapper extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-type.metadata=#lib::Recorded (value: const #lib::Recorded{#lib::Recorded.id: "id"})]
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:2]
final field self::Recorded recorded;
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
core::print(#C4);
}
constants {
#C1 = meta::RecordUse {}
#C2 = "id"
#C3 = self::Recorded {id:#C2}
#C4 = self::Wrapper {recorded:#C3}
}
@@ -0,0 +1,41 @@
{
"constants": [
{
"type": "string",
"value": "id"
},
{
"type": "instance",
"value": {
"id": 0
}
}
],
"locations": [
{
"uri": "nested_instance_constant.dart"
}
],
"metadata": {
"comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
"version": "0.4.0"
},
"recordings": [
{
"definition": {
"identifier": {
"name": "Recorded",
"uri": "nested_instance_constant.dart"
},
"loading_unit": "1"
},
"instances": [
{
"@": 0,
"constant_index": 1,
"loading_unit": "1"
}
]
}
]
}
@@ -5,12 +5,7 @@
import 'package:meta/meta.dart' show RecordUse;
void main() {
doSomething();
}
@MyClass(A.a)
void doSomething() {
print('a');
print(const MyClass(A.a));
}
@RecordUse()
@@ -22,13 +22,7 @@ class A extends core::_Enum /*isEnum*/ {
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
self::doSomething();
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
@#C5
static method doSomething() → void {
core::print("a");
core::print(#C5);
}
constants {
#C1 = meta::RecordUse {}
@@ -5,12 +5,7 @@
import 'package:meta/meta.dart' show RecordUse;
void main() {
doSomething();
}
@MyClass(const A())
void doSomething() {
print('a');
print(const MyClass(const A()));
}
@RecordUse()
@@ -17,13 +17,7 @@ class A extends core::Object /*hasConstConstructor*/ {
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
self::doSomething();
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
@#C3
static method doSomething() → void {
core::print("a");
core::print(#C3);
}
constants {
#C1 = meta::RecordUse {}
+1
View File
@@ -4,6 +4,7 @@
test/cbuilder/cbuilder_cross_android_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
test/cbuilder/cbuilder_cross_linux_host_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
test/cbuilder/cbuilder_cross_macos_host_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
test/cbuilder/cbuilder_cross_windows_host_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
test/clinker/objects_cross_android_test: SkipByDesign # Cross compilation is not tested on the Dart CI.
test/clinker/objects_cross_ios_test: SkipByDesign # Cross compilation is not tested on the Dart CI.