[native_assets] Don't tree-shake `vm:ffi:native-assets` library

Adds a `pragma('vm:entry-point')` to the synthesized library and makes
the tree-shakers respect that pragma on `library`s.

TEST=tests/ffi/native_assets/asset_relative_test.dart

Bug: Found in g3.
Change-Id: I1bc63b42ab867a5f1d2e7f9da4842de59f3d5c1d
Cq-Include-Trybots: dart/try:vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-asan-linux-release-arm64-try,vm-msan-linux-release-arm64-try,vm-tsan-linux-release-arm64-try,vm-ubsan-linux-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/377442
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm/bin/protobuf_aware_treeshaker.dart b/pkg/vm/bin/protobuf_aware_treeshaker.dart
index e7dd01c..fa11fa7 100644
--- a/pkg/vm/bin/protobuf_aware_treeshaker.dart
+++ b/pkg/vm/bin/protobuf_aware_treeshaker.dart
@@ -182,7 +182,8 @@
   return lib.classes.isEmpty &&
       lib.procedures.isEmpty &&
       lib.fields.isEmpty &&
-      lib.typedefs.isEmpty;
+      lib.typedefs.isEmpty &&
+      lib.annotations.isEmpty;
 }
 
 bool isCoreLibrary(Library library) {
diff --git a/pkg/vm/lib/native_assets/synthesizer.dart b/pkg/vm/lib/native_assets/synthesizer.dart
index 4d8321a..75e0160 100644
--- a/pkg/vm/lib/native_assets/synthesizer.dart
+++ b/pkg/vm/lib/native_assets/synthesizer.dart
@@ -67,9 +67,13 @@
       fileUri: _dummyFileUri,
       annotations: [
         ConstantExpression(InstanceConstant(pragmaClass.reference, [], {
+          pragmaName.fieldReference: StringConstant('vm:entry-point'),
+          pragmaOptions.fieldReference: NullConstant(),
+        })),
+        ConstantExpression(InstanceConstant(pragmaClass.reference, [], {
           pragmaName.fieldReference: StringConstant('vm:ffi:native-assets'),
           pragmaOptions.fieldReference: nativeAssetsConstant,
-        }))
+        })),
       ],
     );
   }
diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart
index d08b0ae..5374932 100644
--- a/pkg/vm/lib/transformations/type_flow/native_code.dart
+++ b/pkg/vm/lib/transformations/type_flow/native_code.dart
@@ -66,6 +66,20 @@
   }
 
   @override
+  visitLibrary(Library library) {
+    final type = _annotationsDefineRoot(library.annotations);
+    if (type != null) {
+      if (type == PragmaEntryPointType.Default) {
+        nativeCodeOracle.addLibraryReferencedFromNativeCode(library);
+      } else {
+        throw "Error: pragma entry-point definition on a library must evaluate "
+            "to null. See entry_points_pragma.md.";
+      }
+    }
+    library.visitChildren(this);
+  }
+
+  @override
   visitClass(Class klass) {
     final type = _annotationsDefineRoot(klass.annotations);
     if (type != null) {
@@ -199,10 +213,18 @@
   final Set<Member> _membersReferencedFromNativeCode = new Set<Member>();
   final Set<Member> _dynamicallyOverriddenMembers = new Set<Member>();
   final Set<Class> _classesReferencedFromNativeCode = new Set<Class>();
+  final Set<Library> _librariesReferencedFromNativeCode = new Set<Library>();
   final PragmaAnnotationParser _matcher;
 
   NativeCodeOracle(this._libraryIndex, this._matcher);
 
+  void addLibraryReferencedFromNativeCode(Library library) {
+    _librariesReferencedFromNativeCode.add(library);
+  }
+
+  bool isLibraryReferencedFromNativeCode(Library library) =>
+      _librariesReferencedFromNativeCode.contains(library);
+
   void addClassReferencedFromNativeCode(Class klass) {
     _classesReferencedFromNativeCode.add(klass);
   }
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index 6e4f47f..6bc5097 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -852,6 +852,8 @@
   }
 
   bool isLibraryUsed(Library l) => _usedLibraries.contains(l);
+  bool isLibraryReferencedFromNativeCode(Library l) =>
+      typeFlowAnalysis.nativeCodeOracle.isLibraryReferencedFromNativeCode(l);
   bool isClassReferencedFromNativeCode(Class c) =>
       typeFlowAnalysis.nativeCodeOracle.isClassReferencedFromNativeCode(c);
   bool isClassUsed(Class c) => _usedClasses.contains(c);
@@ -1910,7 +1912,9 @@
 
   @override
   TreeNode visitLibrary(Library node, TreeNode? removalSentinel) {
-    if (!shaker.isLibraryUsed(node) && node.importUri.scheme != 'dart') {
+    if (!shaker.isLibraryUsed(node) &&
+        !shaker.isLibraryReferencedFromNativeCode(node) &&
+        node.importUri.scheme != 'dart') {
       return removalSentinel!;
     }
     _additionalDeps.clear();
diff --git a/pkg/vm/test/native_assets/synthesizer_test.dart b/pkg/vm/test/native_assets/synthesizer_test.dart
index 59538a6..409cb6e 100644
--- a/pkg/vm/test/native_assets/synthesizer_test.dart
+++ b/pkg/vm/test/native_assets/synthesizer_test.dart
@@ -29,20 +29,24 @@
       mode: NonNullableByDefaultCompiledMode.Strong,
     );
     final libraryToString = kernelLibraryToString(component.libraries.single);
-    final expectedKernel = '''@#C9
+    final expectedKernel = '''@#C3
+@#C12
 library;
 import self as self;
 
 constants  {
-  #C1 = "vm:ffi:native-assets"
-  #C2 = "linux_x64"
-  #C3 = "package:foo/foo.dart"
-  #C4 = "absolute"
-  #C5 = "/path/to/libfoo.so"
-  #C6 = <dynamic>[#C4, #C5]
-  #C7 = <dynamic, dynamic>{#C3:#C6}
-  #C8 = <dynamic, dynamic>{#C2:#C7}
-  #C9 = #lib1::pragma {name:#C1, options:#C8}
+  #C1 = "vm:entry-point"
+  #C2 = null
+  #C3 = #lib1::pragma {name:#C1, options:#C2}
+  #C4 = "vm:ffi:native-assets"
+  #C5 = "linux_x64"
+  #C6 = "package:foo/foo.dart"
+  #C7 = "absolute"
+  #C8 = "/path/to/libfoo.so"
+  #C9 = <dynamic>[#C7, #C8]
+  #C10 = <dynamic, dynamic>{#C6:#C9}
+  #C11 = <dynamic, dynamic>{#C5:#C10}
+  #C12 = #lib1::pragma {name:#C4, options:#C11}
 }
 ''';
     expect(libraryToString, equals(expectedKernel));
diff --git a/tests/ffi/native_assets/asset_absolute_test.dart b/tests/ffi/native_assets/asset_absolute_test.dart
index 3e312ae..eb11f3f 100644
--- a/tests/ffi/native_assets/asset_absolute_test.dart
+++ b/tests/ffi/native_assets/asset_absolute_test.dart
@@ -61,6 +61,7 @@
     runtime: Runtime.aot,
     arguments: [runTestsArg],
     nativeAssetsYaml: nativeAssetsYaml,
+    protobufAwareTreeshaking: true,
   );
 }
 
diff --git a/tests/ffi/native_assets/asset_executable_test.dart b/tests/ffi/native_assets/asset_executable_test.dart
index b7f2669..baafe87 100644
--- a/tests/ffi/native_assets/asset_executable_test.dart
+++ b/tests/ffi/native_assets/asset_executable_test.dart
@@ -49,6 +49,7 @@
     runtime: Runtime.jit,
     arguments: [runTestsArg],
     nativeAssetsYaml: nativeAssetsYaml,
+    protobufAwareTreeshaking: true,
   );
   await invokeSelf(
     selfSourceUri: selfSourceUri,
diff --git a/tests/ffi/native_assets/asset_library_annotation_test.dart b/tests/ffi/native_assets/asset_library_annotation_test.dart
index b70eb16..d960239 100644
--- a/tests/ffi/native_assets/asset_library_annotation_test.dart
+++ b/tests/ffi/native_assets/asset_library_annotation_test.dart
@@ -53,6 +53,7 @@
     runtime: Runtime.jit,
     arguments: [runTestsArg],
     nativeAssetsYaml: nativeAssetsYaml,
+    protobufAwareTreeshaking: true,
   );
   await invokeSelf(
     selfSourceUri: selfSourceUri,
diff --git a/tests/ffi/native_assets/asset_process_test.dart b/tests/ffi/native_assets/asset_process_test.dart
index 63b0374..d30a98c 100644
--- a/tests/ffi/native_assets/asset_process_test.dart
+++ b/tests/ffi/native_assets/asset_process_test.dart
@@ -60,6 +60,7 @@
     runtime: Runtime.appjit,
     arguments: [runTestsArg],
     nativeAssetsYaml: nativeAssetsYaml,
+    protobufAwareTreeshaking: true,
   );
   await invokeSelf(
     selfSourceUri: selfSourceUri,
diff --git a/tests/ffi/native_assets/asset_relative_test.dart b/tests/ffi/native_assets/asset_relative_test.dart
index 05bd2ea..c0cfb7a 100644
--- a/tests/ffi/native_assets/asset_relative_test.dart
+++ b/tests/ffi/native_assets/asset_relative_test.dart
@@ -51,6 +51,7 @@
     relativePath: RelativePath.same,
     arguments: [runTestsArg],
     useSymlink: true,
+    protobufAwareTreeshaking: true,
   );
   await invokeSelf(
     selfSourceUri: selfSourceUri,
@@ -58,6 +59,7 @@
     relativePath: RelativePath.down,
     arguments: [runTestsArg],
     useSymlink: true,
+    protobufAwareTreeshaking: false,
   );
   await invokeSelf(
     selfSourceUri: selfSourceUri,
@@ -69,6 +71,7 @@
     relativePath: RelativePath.up,
     arguments: [runTestsArg],
     useSymlink: true,
+    protobufAwareTreeshaking: true,
   );
 }
 
@@ -87,6 +90,7 @@
   AotCompile aotCompile = AotCompile.elf,
   RelativePath relativePath = RelativePath.same,
   bool useSymlink = false,
+  required bool protobufAwareTreeshaking,
 }) async {
   await withTempDir((Uri tempUri) async {
     final nestedUri = tempUri.resolve('nested/');
@@ -123,6 +127,7 @@
       nativeAssetsYaml: nativeAssetsYaml,
       runtime: runtime,
       kernelCombine: kernelCombine,
+      protobufAwareTreeshaking: protobufAwareTreeshaking,
       aotCompile: aotCompile,
       runArguments: arguments,
       useSymlink: useSymlink,
diff --git a/tests/ffi/native_assets/asset_system_test.dart b/tests/ffi/native_assets/asset_system_test.dart
index ede072a..7b1d5ca 100644
--- a/tests/ffi/native_assets/asset_system_test.dart
+++ b/tests/ffi/native_assets/asset_system_test.dart
@@ -68,6 +68,7 @@
     runtime: Runtime.aot,
     arguments: [runTestsArg],
     nativeAssetsYaml: nativeAssetsYaml,
+    protobufAwareTreeshaking: true,
   );
 }
 
diff --git a/tests/ffi/native_assets/helpers.dart b/tests/ffi/native_assets/helpers.dart
index c13ebce..51aa6b3 100644
--- a/tests/ffi/native_assets/helpers.dart
+++ b/tests/ffi/native_assets/helpers.dart
@@ -47,6 +47,9 @@
 final genKernelUri =
     sdkUriAbsolute.resolve('pkg/vm/tool/gen_kernel$standaloneExtension');
 
+final protobufAwareTreeshakerUri =
+    sdkUriAbsolute.resolve('pkg/vm/bin/protobuf_aware_treeshaker.dart');
+
 final genSnapshotUri =
     buildUriAbsolute.resolve('gen_snapshot$standaloneExtensionExe');
 
@@ -80,6 +83,8 @@
     if (!Platform.environment.containsKey(keepTempKey) ||
         Platform.environment[keepTempKey]!.isEmpty) {
       await tempDirResolved.delete(recursive: true);
+    } else {
+      print('Keeping $tempDirResolved');
     }
   }
 }
@@ -161,12 +166,15 @@
   required Uri nativeAssetsUri,
   required Runtime runtime,
   required KernelCombine kernelCombine,
+  required bool protobufAwareTreeshaking,
 }) async {
+  final preTreeshakenDill = tempUri.resolve('pre_treeshaken.dill');
+
   switch (kernelCombine) {
     case KernelCombine.source:
       await runGenKernel(
         runtime: runtime,
-        outputUri: outputUri,
+        outputUri: protobufAwareTreeshaking ? preTreeshakenDill : outputUri,
         inputUri: dartProgramUri,
         nativeAssetsUri: nativeAssetsUri,
       );
@@ -189,7 +197,9 @@
           await File.fromUri(programDillUri).readAsBytes();
       final nativeAssetKernelBytes =
           await File.fromUri(nativeAssetsDillUri).readAsBytes();
-      await File.fromUri(outputUri).writeAsBytes(
+      await File.fromUri(
+        protobufAwareTreeshaking ? preTreeshakenDill : outputUri,
+      ).writeAsBytes(
         [
           ...programKernelBytes,
           ...nativeAssetKernelBytes,
@@ -197,6 +207,17 @@
         flush: true,
       );
   }
+
+  if (protobufAwareTreeshaking) {
+    await runDart(
+      scriptUri: protobufAwareTreeshakerUri,
+      arguments: [
+        if (runtime == Runtime.aot) '--aot',
+        /*<input.dill>*/ preTreeshakenDill.toFilePath(),
+        /*<output.dill>*/ outputUri.toFilePath(),
+      ],
+    );
+  }
 }
 
 Future<void> runGenSnapshot({
@@ -335,6 +356,7 @@
   required String nativeAssetsYaml,
   required Runtime runtime,
   required KernelCombine kernelCombine,
+  required bool protobufAwareTreeshaking,
   AotCompile aotCompile = AotCompile.elf,
   required List<String> runArguments,
   bool useSymlink = false,
@@ -350,6 +372,7 @@
     nativeAssetsUri: nativeAssetsUri,
     runtime: runtime,
     kernelCombine: kernelCombine,
+    protobufAwareTreeshaking: protobufAwareTreeshaking,
   );
 
   switch (runtime) {
@@ -452,6 +475,7 @@
   Runtime runtime = Runtime.jit,
   KernelCombine kernelCombine = KernelCombine.source,
   AotCompile aotCompile = AotCompile.elf,
+  bool protobufAwareTreeshaking = false,
 }) async {
   await withTempDir((Uri tempUri) async {
     await compileAndRun(
@@ -460,6 +484,7 @@
       nativeAssetsYaml: nativeAssetsYaml,
       runtime: runtime,
       kernelCombine: kernelCombine,
+      protobufAwareTreeshaking: protobufAwareTreeshaking,
       aotCompile: aotCompile,
       runArguments: arguments,
     );