Macro. Start implementing reusing cached results.

Change-Id: I35e145da373abb5d8b3065c7e1c5402434351905
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/368160
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index ac14344..becb113 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -41,6 +41,7 @@
 import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/uri_cache.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
+import 'package:collection/collection.dart';
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
 import 'package:meta/meta.dart';
@@ -1731,6 +1732,9 @@
   /// library uses any macros.
   List<AugmentationImportWithFile> _macroImports = const [];
 
+  /// The cache for [apiSignature].
+  Uint8List? _apiSignature;
+
   LibraryCycle? _libraryCycle;
 
   LibraryFileKind({
@@ -1741,6 +1745,22 @@
     file._fsState._libraryNameToFiles.add(this);
   }
 
+  /// The unlinked API signature of all library files.
+  Uint8List get apiSignature {
+    if (_apiSignature case var apiSignature?) {
+      return apiSignature;
+    }
+
+    var builder = ApiSignature();
+
+    var sortedFiles = files.sortedBy((file) => file.path);
+    for (var file in sortedFiles) {
+      builder.addBytes(file.apiSignature);
+    }
+
+    return _apiSignature = builder.toByteList();
+  }
+
   /// All augmentations of this library, in the depth-first pre-order order.
   List<AugmentationFileKind> get augmentations {
     var result = <AugmentationFileKind>[];
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_context.dart b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
index 3aff823..cbde8c6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
@@ -26,6 +26,7 @@
 import 'package:analyzer/src/summary2/link.dart';
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
 import 'package:analyzer/src/summary2/macro.dart';
+import 'package:analyzer/src/summary2/macro_cache.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:collection/collection.dart';
@@ -183,6 +184,16 @@
         }
       }
 
+      var macroResultKey = cycle.cachedMacrosKey;
+      var inputMacroResults = <LibraryFileKind, MacroResultInput>{};
+      if (byteStore.get(macroResultKey) case var bytes?) {
+        _readMacroResults(
+          cycle: cycle,
+          bytes: bytes,
+          macroResults: inputMacroResults,
+        );
+      }
+
       var linkedBytes = byteStore.get(cycle.linkedKey);
 
       if (linkedBytes == null) {
@@ -201,6 +212,7 @@
                 elementFactory: elementFactory,
                 performance: performance,
                 inputLibraries: cycle.libraries,
+                inputMacroResults: inputMacroResults,
                 macroExecutor: this.macroSupport?.executor,
               );
             },
@@ -216,6 +228,12 @@
         testData?.forCycle(cycle).putKeys.add(cycle.linkedKey);
         bytesPut += linkedBytes.length;
 
+        _writeMacroResults(
+          cycle: cycle,
+          linkResult: linkResult,
+          macroResultKey: macroResultKey,
+        );
+
         librariesLinkedTimer.stop();
       } else {
         testData?.forCycle(cycle).getKeys.add(cycle.linkedKey);
@@ -346,6 +364,42 @@
     }
   }
 
+  /// Fills [macroResults] with results that can be reused.
+  void _readMacroResults({
+    required LibraryCycle cycle,
+    required Uint8List bytes,
+    required Map<LibraryFileKind, MacroResultInput> macroResults,
+  }) {
+    var bundle = MacroCacheBundle.fromBytes(cycle, bytes);
+    var testUsedList = <File>[];
+    for (var library in bundle.libraries) {
+      // If the library itself changed, then declarations that macros see
+      // could be different now.
+      if (!ListEquality<int>().equals(
+        library.apiSignature,
+        library.kind.apiSignature,
+      )) {
+        continue;
+      }
+
+      // TODO(scheglov): Record more specific dependencies.
+      if (library.hasAnyIntrospection) {
+        continue;
+      }
+
+      testUsedList.add(library.kind.file.resource);
+
+      macroResults[library.kind] = MacroResultInput(
+        code: library.code,
+      );
+    }
+
+    if (testUsedList.isNotEmpty) {
+      var cycleTestData = testData?.forCycle(cycle);
+      cycleTestData?.macrosUsedCached.add(testUsedList);
+    }
+  }
+
   /// The [exception] was caught during the [cycle] linking.
   ///
   /// Throw another exception that wraps the given one, with more information.
@@ -362,6 +416,37 @@
     }
     throw CaughtExceptionWithFiles(exception, stackTrace, fileContentMap);
   }
+
+  void _writeMacroResults({
+    required LibraryCycle cycle,
+    required LinkResult linkResult,
+    required String macroResultKey,
+  }) {
+    var results = linkResult.macroResults;
+    if (results.isEmpty) {
+      return;
+    }
+
+    testData?.forCycle(cycle).macrosGenerated.add(
+          linkResult.macroResults
+              .map((result) => result.library.file.resource)
+              .toList(),
+        );
+
+    var bundle = MacroCacheBundle(
+      libraries: results.map((result) {
+        return MacroCacheLibrary(
+          kind: result.library,
+          apiSignature: result.library.apiSignature,
+          hasAnyIntrospection: result.processing.hasAnyIntrospection,
+          code: result.code,
+        );
+      }).toList(),
+    );
+
+    var bytes = bundle.toBytes();
+    byteStore.putGet(macroResultKey, bytes);
+  }
 }
 
 class LibraryContextTestData {
@@ -395,6 +480,8 @@
 class LibraryCycleTestData {
   final List<String> getKeys = [];
   final List<String> putKeys = [];
+  final List<List<File>> macrosUsedCached = [];
+  final List<List<File>> macrosGenerated = [];
 }
 
 class _MacroFileEntry implements MacroFileEntry {
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index 5958654..0fc73d4 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -7,9 +7,11 @@
 
 import 'package:_fe_analyzer_shared/src/util/dependency_walker.dart' as graph
     show DependencyWalker, Node;
+import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
 import 'package:analyzer/src/utilities/extensions/collection.dart';
+import 'package:collection/collection.dart';
 
 /// Ensure that the [FileState.libraryCycle] for the [file] and anything it
 /// depends on is computed.
@@ -59,6 +61,12 @@
   /// include [implSignature] of the macro defining library.
   final String implSignature;
 
+  /// The transitive macro implementation signature of this cycle.
+  ///
+  /// It is based on full code signatures of all files that might affect
+  /// macro implementation code.
+  final String implSignatureMacro;
+
   late final bool declaresMacroClass = () {
     for (var library in libraries) {
       for (var file in library.files) {
@@ -96,12 +104,31 @@
     required this.directDependencies,
     required this.apiSignature,
     required this.implSignature,
+    required this.implSignatureMacro,
   }) {
     for (var directDependency in directDependencies) {
       directDependency.directUsers.add(this);
     }
   }
 
+  /// The key to store the bundle with multiple libraries, containing
+  /// potentially reusable macro generated code for each library.
+  String get cachedMacrosKey {
+    var builder = ApiSignature();
+    builder.addInt(AnalysisDriver.DATA_VERSION);
+
+    builder.addString(implSignatureMacro);
+
+    var sortedLibraries = libraries.sortedBy((l) => l.file.path);
+    for (var library in sortedLibraries) {
+      builder.addString(library.file.path);
+      builder.addString(library.file.uriStr);
+    }
+
+    var keyHex = builder.toHex();
+    return '$keyHex.macro_results';
+  }
+
   /// The key of the linked libraries in the byte store.
   String get linkedKey => '$apiSignature.linked';
 
@@ -193,8 +220,10 @@
   void evaluateScc(List<_LibraryNode> scc) {
     var apiSignature = ApiSignature();
     var implSignature = ApiSignature();
+    var implSignature2 = ApiSignature();
     apiSignature.addUint32List(_salt);
     implSignature.addUint32List(_salt);
+    implSignature2.addUint32List(_salt);
 
     // Sort libraries to produce stable signatures.
     scc.sort((first, second) {
@@ -210,6 +239,7 @@
         directDependencies,
         apiSignature,
         implSignature,
+        implSignature2,
         graph.Node.getDependencies(node),
       );
     }
@@ -251,6 +281,7 @@
       directDependencies: directDependencies,
       apiSignature: apiSignature.toHex(),
       implSignature: implSignature.toHex(),
+      implSignatureMacro: implSignature2.toHex(),
     );
 
     if (cycle.declaresMacroClass) {
@@ -271,6 +302,7 @@
     Set<LibraryCycle> directDependencies,
     ApiSignature apiSignature,
     ApiSignature implSignature,
+    ApiSignature implSignatureMacro,
     List<_LibraryNode> directlyReferenced,
   ) {
     apiSignature.addInt(directlyReferenced.length);
@@ -282,11 +314,12 @@
       if (referencedCycle == null) continue;
 
       if (directDependencies.add(referencedCycle)) {
-        apiSignature.addString(
-          referencedCycle.declaresMacroClass
-              ? referencedCycle.implSignature
-              : referencedCycle.apiSignature,
-        );
+        if (referencedCycle.declaresMacroClass) {
+          apiSignature.addString(referencedCycle.implSignature);
+          implSignatureMacro.addString(referencedCycle.implSignature);
+        } else {
+          apiSignature.addString(referencedCycle.apiSignature);
+        }
         implSignature.addString(referencedCycle.implSignature);
       }
     }
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index 05c3704..c873379 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -108,6 +108,12 @@
   /// written or macro generated.
   final Set<ConstFieldElementImpl> finalInstanceFields = Set.identity();
 
+  /// Set if the library reuses the cached macro result.
+  AugmentationImportWithFile? inputMacroAugmentationImport;
+
+  /// The sink for macro applying facts, for caching.
+  final MacroProcessing macroProcessing = MacroProcessing();
+
   final List<List<macro.MacroExecutionResult>> _macroResults = [];
 
   LibraryBuilder._({
@@ -460,6 +466,27 @@
     return _augmentedBuilders[name];
   }
 
+  MacroResultOutput? getCacheableMacroResult() {
+    // Nothing if we already reuse a cached result.
+    if (inputMacroAugmentationImport != null) {
+      return null;
+    }
+
+    var macroImport = kind.augmentationImports.lastOrNull;
+    if (macroImport is AugmentationImportWithFile) {
+      var importedFile = macroImport.importedFile;
+      if (importedFile.isMacroAugmentation) {
+        return MacroResultOutput(
+          library: kind,
+          processing: macroProcessing,
+          code: importedFile.content,
+        );
+      }
+    }
+
+    return null;
+  }
+
   /// Merges accumulated [_macroResults] and corresponding macro augmentation
   /// libraries into a single macro augmentation library.
   Future<void> mergeMacroAugmentations({
@@ -738,6 +765,19 @@
     _augmentationTargets[name] = augmentation;
   }
 
+  /// Updates the element of the macro augmentation.
+  void updateInputMacroAugmentation() {
+    if (inputMacroAugmentationImport case var import?) {
+      var augmentation = element.augmentations.last;
+      var importedFile = import.importedFile;
+      var informativeBytes = importedFile.unlinked2.informativeBytes;
+      augmentation.macroGenerated = MacroGeneratedAugmentationLibrary(
+        code: importedFile.content,
+        informativeBytes: informativeBytes,
+      );
+    }
+  }
+
   LibraryAugmentationElementImpl _addMacroAugmentation(
     AugmentationImportWithFile state,
   ) {
@@ -1146,7 +1186,11 @@
     }
   }
 
-  static void build(Linker linker, LibraryFileKind inputLibrary) {
+  static void build(
+    Linker linker,
+    LibraryFileKind inputLibrary,
+    MacroResultInput? inputMacroResult,
+  ) {
     var elementFactory = linker.elementFactory;
     var rootReference = linker.rootReference;
 
@@ -1288,6 +1332,14 @@
       units: linkingUnits,
     );
 
+    if (inputMacroResult != null) {
+      var import = inputLibrary.addMacroAugmentation(
+        inputMacroResult.code,
+        partialIndex: null,
+      );
+      builder.inputMacroAugmentationImport = import;
+    }
+
     linker.builders[builder.uri] = builder;
   }
 
diff --git a/pkg/analyzer/lib/src/summary2/link.dart b/pkg/analyzer/lib/src/summary2/link.dart
index 5c7c2f0..6c91105 100644
--- a/pkg/analyzer/lib/src/summary2/link.dart
+++ b/pkg/analyzer/lib/src/summary2/link.dart
@@ -26,6 +26,7 @@
 import 'package:analyzer/src/summary2/types_builder.dart';
 import 'package:analyzer/src/summary2/variance_builder.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/uri_cache.dart';
 import 'package:macros/src/executor/multi_executor.dart' as macro;
 
@@ -33,15 +34,25 @@
   required LinkedElementFactory elementFactory,
   required OperationPerformanceImpl performance,
   required List<LibraryFileKind> inputLibraries,
+  required Map<LibraryFileKind, MacroResultInput> inputMacroResults,
   macro.MultiMacroExecutor? macroExecutor,
 }) async {
   var linker = Linker(elementFactory, macroExecutor);
   await linker.link(
     performance: performance,
     inputLibraries: inputLibraries,
+    inputMacroResults: inputMacroResults,
   );
+
+  var macroResultsOutput = <MacroResultOutput>[];
+  for (var builder in linker.builders.values) {
+    var result = builder.getCacheableMacroResult();
+    macroResultsOutput.addIfNotNull(result);
+  }
+
   return LinkResult(
     resolutionBytes: linker.resolutionBytes,
+    macroResults: macroResultsOutput,
   );
 }
 
@@ -93,9 +104,11 @@
   Future<void> link({
     required OperationPerformanceImpl performance,
     required List<LibraryFileKind> inputLibraries,
+    required Map<LibraryFileKind, MacroResultInput> inputMacroResults,
   }) async {
     for (var inputLibrary in inputLibraries) {
-      LibraryBuilder.build(this, inputLibrary);
+      var inputMacroResult = inputMacroResults[inputLibrary];
+      LibraryBuilder.build(this, inputLibrary, inputMacroResult);
     }
 
     await _buildOutlines(
@@ -195,7 +208,9 @@
     );
 
     for (var library in builders.values) {
-      await library.fillMacroApplier(macroApplier);
+      if (library.inputMacroAugmentationImport == null) {
+        await library.fillMacroApplier(macroApplier);
+      }
     }
 
     return _macroApplier = macroApplier;
@@ -266,6 +281,9 @@
     );
 
     _disposeMacroApplications();
+    for (var library in builders.values) {
+      library.updateInputMacroAugmentation();
+    }
   }
 
   void _collectMixinSuperInvokedNames() {
@@ -457,7 +475,32 @@
 class LinkResult {
   final Uint8List resolutionBytes;
 
+  /// The results of applying macros in libraries.
+  final List<MacroResultOutput> macroResults;
+
   LinkResult({
     required this.resolutionBytes,
+    required this.macroResults,
+  });
+}
+
+class MacroResultInput {
+  final String code;
+
+  MacroResultInput({
+    required this.code,
+  });
+}
+
+/// The results of applying macros in [library].
+class MacroResultOutput {
+  final LibraryFileKind library;
+  final MacroProcessing processing;
+  final String code;
+
+  MacroResultOutput({
+    required this.library,
+    required this.processing,
+    required this.code,
   });
 }
diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart
index 1def9f1..97a56bd 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application.dart
@@ -70,6 +70,7 @@
   }
 }
 
+// TODO(scheglov): The name is deceptive, this is not for a single library.
 class LibraryMacroApplier {
   @visibleForTesting
   static bool testThrowExceptionTypes = false;
@@ -95,8 +96,12 @@
     required OperationPerformanceImpl performance,
   }) runDeclarationsPhase;
 
-  /// The applications that currently run the declarations phase.
-  final List<_MacroApplication> _declarationsPhaseRunning = [];
+  /// The applications that currently run.
+  ///
+  /// There can be more than one, because during the declarations phase we
+  /// can start introspecting another declaration, which runs the declarations
+  /// phase for that another declaration.
+  final List<_MacroApplication> _runningApplications = [];
 
   /// The map from a declaration that has declarations phase introspection
   /// cycle, to the cycle exception.
@@ -105,6 +110,7 @@
 
   late final macro.TypePhaseIntrospector _typesPhaseIntrospector =
       _TypePhaseIntrospector(
+    this,
     elementFactory,
     declarationBuilder,
     OperationPerformanceImpl('<typesPhaseIntrospector>'),
@@ -118,6 +124,11 @@
     required this.runDeclarationsPhase,
   });
 
+  /// The currently running macro application.
+  _MacroApplication? get currentApplication {
+    return _runningApplications.lastOrNull;
+  }
+
   Future<void> add({
     required LibraryBuilder libraryBuilder,
     required LibraryOrAugmentationElementImpl container,
@@ -292,10 +303,10 @@
     required OperationPerformanceImpl performance,
   }) async {
     if (targetElement != null) {
-      for (var i = 0; i < _declarationsPhaseRunning.length; i++) {
-        var running = _declarationsPhaseRunning[i];
+      for (var i = 0; i < _runningApplications.length; i++) {
+        var running = _runningApplications[i];
         if (running.target.element == targetElement) {
-          var applications = _declarationsPhaseRunning.sublist(i);
+          var applications = _runningApplications.sublist(i);
           var exception = _MacroIntrospectionCycleException(
             'Declarations phase introspection cycle.',
             applications: applications,
@@ -321,8 +332,7 @@
       return null;
     }
 
-    _declarationsPhaseRunning.add(application);
-
+    _runningApplications.add(application);
     var results = <macro.MacroExecutionResult>[];
 
     await _runWithCatchingExceptions(
@@ -330,10 +340,10 @@
         var target = _buildTarget(application.target.node);
 
         var introspector = _DeclarationPhaseIntrospector(
+          this,
           elementFactory,
           declarationBuilder,
           performance,
-          this,
           libraryBuilder.element.typeSystem,
         );
 
@@ -356,7 +366,7 @@
       annotationIndex: application.annotationIndex,
     );
 
-    _declarationsPhaseRunning.remove(application);
+    _runningApplications.removeLast();
     return ApplicationResult(application, results);
   }
 
@@ -371,6 +381,7 @@
       return null;
     }
 
+    _runningApplications.add(application);
     var results = <macro.MacroExecutionResult>[];
 
     await _runWithCatchingExceptions(
@@ -378,10 +389,10 @@
         var target = _buildTarget(application.target.node);
 
         var introspector = _DefinitionPhaseIntrospector(
+          this,
           elementFactory,
           declarationBuilder,
           performance,
-          this,
           application.target.library.element.typeSystem,
         );
 
@@ -404,6 +415,7 @@
       annotationIndex: application.annotationIndex,
     );
 
+    _runningApplications.removeLast();
     return ApplicationResult(application, results);
   }
 
@@ -417,6 +429,7 @@
       return null;
     }
 
+    _runningApplications.add(application);
     var results = <macro.MacroExecutionResult>[];
 
     await _runWithCatchingExceptions(
@@ -442,6 +455,7 @@
       annotationIndex: application.annotationIndex,
     );
 
+    _runningApplications.removeLast();
     return ApplicationResult(application, results);
   }
 
@@ -909,6 +923,11 @@
   final List<_MacroApplication> _applications = [];
 }
 
+/// Facts about applying macros in a library.
+class MacroProcessing {
+  bool hasAnyIntrospection = false;
+}
+
 class _AnnotationMacro {
   final LibraryElementImpl macroLibrary;
   final BundleMacroExecutor bundleExecutor;
@@ -1048,14 +1067,13 @@
 
 class _DeclarationPhaseIntrospector extends _TypePhaseIntrospector
     implements macro.DeclarationPhaseIntrospector {
-  final LibraryMacroApplier applier;
   final TypeSystemImpl typeSystem;
 
   _DeclarationPhaseIntrospector(
+    super.applier,
     super.elementFactory,
     super.declarationBuilder,
     super.performance,
-    this.applier,
     this.typeSystem,
   );
 
@@ -1068,6 +1086,7 @@
     var element = (type as HasElement).element;
     await _runDeclarationsPhase(element);
 
+    macroProcessing?.hasAnyIntrospection = true;
     if (element case InterfaceElement(:var augmented)) {
       return augmented.constructors
           .map((e) => e.declaration as ConstructorElementImpl)
@@ -1089,6 +1108,7 @@
     var element = (type as HasElement).element;
     await _runDeclarationsPhase(element);
 
+    macroProcessing?.hasAnyIntrospection = true;
     if (element case InstanceElement(:var augmented)) {
       return augmented.fields
           .whereNot((e) => e.isSynthetic)
@@ -1110,6 +1130,7 @@
     var element = (type as HasElement).element;
     await _runDeclarationsPhase(element);
 
+    macroProcessing?.hasAnyIntrospection = true;
     if (element case InstanceElement(:var augmented)) {
       return [
         ...augmented.accessors.whereNot((e) => e.isSynthetic),
@@ -1127,6 +1148,7 @@
   @override
   Future<macro.StaticType> resolve(macro.TypeAnnotationCode typeCode) async {
     performance.getDataInt('resolve').increment();
+    macroProcessing?.hasAnyIntrospection = true;
     var type = declarationBuilder.resolveType(typeCode);
     return _StaticTypeImpl(typeSystem, type);
   }
@@ -1136,6 +1158,7 @@
     macro.Identifier identifier,
   ) async {
     performance.getDataInt('typeDeclarationOf').increment();
+    macroProcessing?.hasAnyIntrospection = true;
     return declarationBuilder.typeDeclarationOf(identifier);
   }
 
@@ -1143,6 +1166,7 @@
   Future<List<macro.TypeDeclaration>> typesOf(
     covariant LibraryImplFromElement library,
   ) async {
+    macroProcessing?.hasAnyIntrospection = true;
     return library.element.topLevelElements
         .map((e) => declarationBuilder.declarationOfElement(e))
         .whereType<macro.TypeDeclaration>()
@@ -1157,6 +1181,8 @@
     await _runDeclarationsPhase(element);
 
     element as EnumElementImpl;
+    macroProcessing?.hasAnyIntrospection = true;
+
     // TODO(scheglov): use augmented
     return element.constants
         .map(declarationBuilder.declarationOfElement)
@@ -1166,7 +1192,7 @@
 
   Future<void> _runDeclarationsPhase(ElementImpl element) async {
     // Don't run for the current element.
-    var current = applier._declarationsPhaseRunning.lastOrNull;
+    var current = applier._runningApplications.lastOrNull;
     if (current?.target.element == element) {
       return;
     }
@@ -1189,10 +1215,10 @@
 class _DefinitionPhaseIntrospector extends _DeclarationPhaseIntrospector
     implements macro.DefinitionPhaseIntrospector {
   _DefinitionPhaseIntrospector(
+    super.applier,
     super.elementFactory,
     super.declarationBuilder,
     super.performance,
-    super.applier,
     super.typeSystem,
   );
 
@@ -1214,6 +1240,7 @@
   Future<List<macro.Declaration>> topLevelDeclarationsOf(
     covariant LibraryImplFromElement library,
   ) async {
+    macroProcessing?.hasAnyIntrospection = true;
     return library.element.topLevelElements
         .whereNot((e) => e.isSynthetic)
         .map((e) => declarationBuilder.declarationOfElement(e))
@@ -1289,24 +1316,33 @@
 }
 
 class _TypePhaseIntrospector implements macro.TypePhaseIntrospector {
+  final LibraryMacroApplier applier;
   final LinkedElementFactory elementFactory;
   final DeclarationBuilder declarationBuilder;
   final OperationPerformanceImpl performance;
 
   _TypePhaseIntrospector(
+    this.applier,
     this.elementFactory,
     this.declarationBuilder,
     this.performance,
   );
 
+  MacroProcessing? get macroProcessing {
+    return applier.currentApplication?.target.library.macroProcessing;
+  }
+
   @override
   Future<macro.Identifier> resolveIdentifier(Uri library, String name) async {
     var libraryElement = elementFactory.libraryOfUri2(library);
+    macroProcessing?.hasAnyIntrospection = true;
+
     var lookup = libraryElement.scope.lookup(name);
     var element = lookup.getter ?? lookup.setter;
     if (element is PropertyAccessorElement && element.isSynthetic) {
       element = element.variable2;
     }
+
     if (element == null) {
       throw macro.MacroImplementationExceptionImpl(
         [
@@ -1317,6 +1353,7 @@
         stackTrace: StackTrace.current.toString(),
       );
     }
+
     return declarationBuilder.fromElement.identifier(element);
   }
 }
diff --git a/pkg/analyzer/lib/src/summary2/macro_cache.dart b/pkg/analyzer/lib/src/summary2/macro_cache.dart
new file mode 100644
index 0000000..866c916
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary2/macro_cache.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2024, 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 'dart:typed_data';
+
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/library_graph.dart';
+import 'package:analyzer/src/summary2/data_reader.dart';
+import 'package:analyzer/src/summary2/data_writer.dart';
+
+class MacroCacheBundle {
+  final List<MacroCacheLibrary> libraries;
+
+  MacroCacheBundle({
+    required this.libraries,
+  });
+
+  factory MacroCacheBundle.fromBytes(
+    LibraryCycle cycle,
+    Uint8List bytes,
+  ) {
+    return MacroCacheBundle.read(
+      cycle,
+      SummaryDataReader(bytes),
+    );
+  }
+
+  factory MacroCacheBundle.read(
+    LibraryCycle cycle,
+    SummaryDataReader reader,
+  ) {
+    return MacroCacheBundle(
+      libraries: reader.readTypedList(
+        () => MacroCacheLibrary.read(cycle, reader),
+      ),
+    );
+  }
+
+  Uint8List toBytes() {
+    var byteSink = ByteSink();
+    var sink = BufferedSink(byteSink);
+    write(sink);
+    return sink.flushAndTake();
+  }
+
+  void write(BufferedSink sink) {
+    sink.writeList(libraries, (library) {
+      library.write(sink);
+    });
+  }
+}
+
+class MacroCacheLibrary {
+  /// The file view of the library.
+  final LibraryFileKind kind;
+
+  /// The combination of API signatures of all library files.
+  final Uint8List apiSignature;
+
+  /// Whether any macro of the library introspected anything.
+  final bool hasAnyIntrospection;
+
+  final String code;
+
+  MacroCacheLibrary({
+    required this.kind,
+    required this.apiSignature,
+    required this.hasAnyIntrospection,
+    required this.code,
+  });
+
+  factory MacroCacheLibrary.read(
+    LibraryCycle cycle,
+    SummaryDataReader reader,
+  ) {
+    var path = reader.readStringUtf8();
+    return MacroCacheLibrary(
+      // This is safe because the key of the bundle depends on paths.
+      kind: cycle.libraries.firstWhere((e) => e.file.path == path),
+      apiSignature: reader.readUint8List(),
+      hasAnyIntrospection: reader.readBool(),
+      code: reader.readStringUtf8(),
+    );
+  }
+
+  void write(BufferedSink sink) {
+    sink.writeStringUtf8(kind.file.path);
+    sink.writeUint8List(apiSignature);
+    sink.writeBool(hasAnyIntrospection);
+    sink.writeStringUtf8(code);
+  }
+}
diff --git a/pkg/analyzer/test/src/summary/elements_base.dart b/pkg/analyzer/test/src/summary/elements_base.dart
index 3344525..2877a12 100644
--- a/pkg/analyzer/test/src/summary/elements_base.dart
+++ b/pkg/analyzer/test/src/summary/elements_base.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -25,12 +26,12 @@
     newFile(path, contents);
   }
 
-  Future<LibraryElementImpl> buildLibrary(String text) async {
-    var file = newFile(testFile.path, text);
+  Future<LibraryElementImpl> buildFileLibrary(File file) async {
     var analysisContext = contextFor(file);
     var analysisSession = analysisContext.currentSession;
 
-    var uriStr = 'package:test/test.dart';
+    var uri = analysisSession.uriConverter.pathToUri(file.path);
+    var uriStr = uri.toString();
     var libraryResult = await analysisSession.getLibraryByUri(uriStr);
 
     if (keepLinkingLibraries) {
@@ -43,6 +44,11 @@
     }
   }
 
+  Future<LibraryElementImpl> buildLibrary(String text) async {
+    var file = newFile(testFile.path, text);
+    return await buildFileLibrary(file);
+  }
+
   void checkElementText(LibraryElementImpl library, String expected) {
     var actual = getLibraryText(
       library: library,
diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart
index c0a4645..6b7091f 100644
--- a/pkg/analyzer/test/src/summary/macro_test.dart
+++ b/pkg/analyzer/test/src/summary/macro_test.dart
@@ -15,12 +15,15 @@
 import 'package:analyzer/src/summary2/macro.dart';
 import 'package:analyzer/src/summary2/macro_application.dart';
 import 'package:analyzer/src/summary2/macro_application_error.dart';
+import 'package:analyzer/src/utilities/extensions/file_system.dart';
+import 'package:collection/collection.dart';
 import 'package:macros/src/bootstrap.dart' as macro;
 import 'package:macros/src/executor/serialization.dart' as macro;
 import 'package:path/path.dart' as package_path;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import '../../util/tree_string_sink.dart';
 import '../dart/resolution/context_collection_resolution.dart';
 import '../dart/resolution/node_text_expectations.dart';
 import 'element_text.dart';
@@ -38,6 +41,7 @@
 
   defineReflectiveSuite(() {
     defineReflectiveTests(MacroArgumentsTest);
+    defineReflectiveTests(MacroIncrementalTest);
     defineReflectiveTests(MacroIntrospectNodeTest);
     defineReflectiveTests(MacroIntrospectNodeDefinitionsTest);
     defineReflectiveTests(MacroIntrospectElementTest);
@@ -9148,6 +9152,222 @@
 }
 
 @reflectiveTest
+class MacroIncrementalTest extends MacroElementsBaseTest {
+  @override
+  bool get keepLinkingLibraries => true;
+
+  @override
+  bool get retainDataForTesting => true;
+
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
+    useEmptyByteStore();
+  }
+
+  test_changeDependency_noIntrospect() async {
+    var a = newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+    var library1 = await buildLibrary(r'''
+import 'append.dart';
+import 'a.dart';
+
+@DeclareInType('  void foo() {}')
+class X {}
+''');
+
+    var expectedLibraryText = r'''
+library
+  imports
+    package:test/append.dart
+    package:test/a.dart
+  definingUnit
+    classes
+      class X @80
+        augmentation: self::@augmentation::package:test/test.macro.dart::@classAugmentation::X
+        augmented
+          methods
+            self::@augmentation::package:test/test.macro.dart::@classAugmentation::X::@method::foo
+  augmentationImports
+    package:test/test.macro.dart
+      macroGeneratedCode
+---
+augment library 'package:test/test.dart';
+
+augment class X {
+  void foo() {}
+}
+---
+      definingUnit
+        classes
+          augment class X @57
+            augmentationTarget: self::@class::X
+            methods
+              foo @68
+                returnType: void
+''';
+
+    configuration
+      ..withConstructors = false
+      ..withMetadata = false;
+    checkElementText(library1, expectedLibraryText);
+
+    // Generated and put into the cache.
+    _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+  usedCached
+  generated
+    /home/test/lib/test.dart
+''');
+
+    modifyFile2(a, r'''
+class A2 {}
+''');
+    driverFor(testFile).changeFile2(a);
+    await contextFor(testFile).applyPendingFileChanges();
+
+    var library2 = await buildFileLibrary(testFile);
+    checkElementText(library2, expectedLibraryText);
+
+    // Used cached, no new generated.
+    _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+  usedCached
+    /home/test/lib/test.dart
+  generated
+    /home/test/lib/test.dart
+''');
+  }
+
+  test_resolveIdentifier_class_notChanged() async {
+    var a = newFile('$testPackageLibPath/a.dart', r'''
+class A {}
+''');
+
+    var library1 = await buildLibrary(r'''
+import 'append.dart';
+import 'a.dart';
+
+@DeclareInType('  {{package:test/test.dart@A}} foo() {}')
+class X {}
+''');
+
+    var expectedLibraryText = r'''
+library
+  imports
+    package:test/append.dart
+    package:test/a.dart
+  definingUnit
+    classes
+      class X @104
+        augmentation: self::@augmentation::package:test/test.macro.dart::@classAugmentation::X
+        augmented
+          methods
+            self::@augmentation::package:test/test.macro.dart::@classAugmentation::X::@method::foo
+  augmentationImports
+    package:test/test.macro.dart
+      macroGeneratedCode
+---
+augment library 'package:test/test.dart';
+
+import 'package:test/a.dart' as prefix0;
+
+augment class X {
+  prefix0.A foo() {}
+}
+---
+      imports
+        package:test/a.dart as prefix0 @75
+      definingUnit
+        classes
+          augment class X @99
+            augmentationTarget: self::@class::X
+            methods
+              foo @115
+                returnType: A
+''';
+
+    configuration
+      ..withConstructors = false
+      ..withMetadata = false;
+    checkElementText(library1, expectedLibraryText);
+
+    // Generated and put into the cache.
+    _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+  usedCached
+  generated
+    /home/test/lib/test.dart
+''');
+
+    modifyFile2(a, r'''
+class A {}
+class B {}
+''');
+    driverFor(testFile).changeFile2(a);
+    await contextFor(testFile).applyPendingFileChanges();
+
+    var library2 = await buildFileLibrary(testFile);
+    checkElementText(library2, expectedLibraryText);
+
+    // Cannot prove that `package:test/test.dart@A` is still there.
+    // So, not used cached.
+    // TODO(scheglov): Make it use cached.
+    _assertMacroCachedGenerated(testFile, r'''
+/home/test/lib/test.dart
+  usedCached
+  generated
+    /home/test/lib/test.dart
+    /home/test/lib/test.dart
+''');
+  }
+
+  void _assertMacroCachedGenerated(File targetFile, String expected) {
+    var buffer = StringBuffer();
+    var sink = TreeStringSink(
+      sink: buffer,
+      indent: '',
+    );
+
+    var testData = driverFor(testFile).testView!.libraryContext;
+    for (var entry in testData.libraryCycles.entries) {
+      if (entry.key.map((fd) => fd.file).contains(targetFile)) {
+        var fileListStr = entry.key
+            .map((fileData) => fileData.file.posixPath)
+            .sorted()
+            .join(' ');
+        sink.writelnWithIndent(fileListStr);
+        sink.withIndent(() {
+          sink.writelnWithIndent('usedCached');
+          sink.withIndent(() {
+            for (var files in entry.value.macrosUsedCached) {
+              sink.writelnWithIndent(files.map((f) => f.posixPath).join(' '));
+            }
+          });
+
+          sink.writelnWithIndent('generated');
+          sink.withIndent(() {
+            for (var files in entry.value.macrosGenerated) {
+              sink.writelnWithIndent(files.map((f) => f.posixPath).join(' '));
+            }
+          });
+        });
+      }
+    }
+
+    var actual = buffer.toString();
+    if (actual != expected) {
+      print('-------- Actual --------');
+      print('$actual------------------------');
+      NodeTextExpectationsCollector.add(actual);
+    }
+    expect(actual, expected);
+  }
+}
+
+@reflectiveTest
 class MacroIntrospectElementTest extends MacroElementsBaseTest {
   @override
   bool get keepLinkingLibraries => true;