Add drop state nodes flag and apply it to `SkyframeProgressReceiver#evaluated()` method.

Due to the fact that the vast majority of the `_STATE` nodes only have one rdeps, we decided to drop them after the evaluation of `FILE` or `DIRECTORY_LISTING` nodes. So in this change:

* A new `--experimental_drop_state_nodes` is added in `CommonCommandOptions` in order to control whether `_STATE` nodes should be dropped or not;
* Inject the flag to `SequencedSkyframeExecutor` and `SkybuildV2SkyframeExecutor`;
* `SkyframeProgressReceiver#evaluated(...)` method decides whether to drop `_STATE` nodes based on the flag value and the type of input `SkyKey`.

PiperOrigin-RevId: 495949249
Change-Id: I7b4b84f7d91e8c3a7ce44f5ccb1af62ed134ebeb
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index 432f787..60e9b66 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -758,7 +758,9 @@
     AnalysisOptions viewOptions = options.getOptions(AnalysisOptions.class);
     skyframeExecutor.decideKeepIncrementalState(
         runtime.getStartupOptionsProvider().getOptions(BlazeServerStartupOptions.class).batch,
-        commonOptions.keepStateAfterBuild, commonOptions.trackIncrementalState,
+        commonOptions.keepStateAfterBuild,
+        commonOptions.trackIncrementalState,
+        commonOptions.experimentalHeuristicallyDropNodes,
         viewOptions != null && viewOptions.discardAnalysisCache,
         reporter);
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommonCommandOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/CommonCommandOptions.java
index a4b79f6..ef71cc9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommonCommandOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommonCommandOptions.java
@@ -535,6 +535,18 @@
               + " invalidating the action graph.")
   public List<Map.Entry<String, String>> repositoryEnvironment;
 
+  @Option(
+      name = "experimental_heuristically_drop_nodes",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
+      effectTags = {OptionEffectTag.LOSES_INCREMENTAL_STATE},
+      help =
+          "If true, Blaze will remove FileState and DirectoryListingState nodes after related File"
+              + " and DirectoryListing node is done to save memory. We expect that it is less"
+              + " likely that these nodes will be needed again. If so, the program will re-evaluate"
+              + " them.")
+  public boolean experimentalHeuristicallyDropNodes;
+
   /** The option converter to check that the user can only specify legal profiler tasks. */
   public static class ProfilerTaskConverter extends EnumConverter<ProfilerTask> {
     public ProfilerTaskConverter() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index 53bc922..7e6bffb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -667,6 +667,7 @@
       boolean batch,
       boolean keepStateAfterBuild,
       boolean shouldTrackIncrementalState,
+      boolean heuristicallyDropNodes,
       boolean discardAnalysisCache,
       EventHandler eventHandler) {
     Preconditions.checkState(!active);
@@ -696,6 +697,19 @@
       }
     }
 
+    if (trackIncrementalState) {
+      if (heuristicallyDropNodes) {
+        eventHandler.handle(
+            Event.warn(
+                "--experimental_heuristically_drop_nodes was specified with track incremental state"
+                    + " also being true. The flag is ignored and no node is heuristically dropped"
+                    + " in the track incremental mode."));
+      }
+      this.heuristicallyDropNodes = false;
+    } else {
+      this.heuristicallyDropNodes = heuristicallyDropNodes;
+    }
+
     // Now check if it is necessary to wipe the previous state. We do this if either the previous
     // or current incrementalStateRetentionStrategy requires the build to have been isolated.
     if (oldValueOfTrackIncrementalState != trackIncrementalState) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index a584b9e..5e38075 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -401,6 +401,11 @@
   // Reset while preparing for execution in each build.
   private Optional<IncrementalPackageRoots> incrementalPackageRoots = Optional.empty();
 
+  // This boolean controls whether FILE_STATE or DIRECTORY_LISTING_STATE nodes are dropped after the
+  // corresponding FILE or DIRECTORY_LISTING nodes are evaluated.
+  // See b/261019506.
+  protected boolean heuristicallyDropNodes = false;
+
   class PathResolverFactoryImpl implements PathResolverFactory {
     @Override
     public boolean shouldCreatePathResolverForArtifactValues() {
@@ -948,6 +953,7 @@
       boolean batch,
       boolean keepStateAfterBuild,
       boolean trackIncrementalState,
+      boolean heuristicallyDropNodes,
       boolean discardAnalysisCache,
       EventHandler eventHandler) {
     // Assume incrementality.
@@ -3100,6 +3106,26 @@
         @Nullable ErrorInfo newError,
         Supplier<EvaluationSuccessState> evaluationSuccessState,
         EvaluationState state) {
+      if (heuristicallyDropNodes) {
+        Object argument = skyKey.argument();
+        if (skyKey.functionName().equals(FileValue.FILE)) {
+          Preconditions.checkArgument(
+              argument instanceof RootedPath,
+              "FILE SkyKey (%s) does not have a RootedPath typed argument (%s)",
+              skyKey,
+              argument);
+          memoizingEvaluator.getAllValuesMutable().remove(argument);
+        } else if (skyKey.functionName().equals(SkyFunctions.DIRECTORY_LISTING)) {
+          Preconditions.checkArgument(
+              argument instanceof RootedPath,
+              "DIRECTORY_LISTING SkyKey (%s) does not have a RootedPath typed argument (%s)",
+              skyKey,
+              argument);
+          SkyKey directoryListingStateKey = DirectoryListingStateValue.key((RootedPath) argument);
+          memoizingEvaluator.getAllValuesMutable().remove(directoryListingStateKey);
+        }
+      }
+
       if (EvaluationState.BUILT.equals(state)) {
         skyKeyStateReceiver.evaluated(skyKey);
       }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
index 5f4bedc..fa7e702 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
@@ -1590,10 +1590,11 @@
     initializeSkyframeExecutor();
     skyframeExecutor.setActive(false);
     skyframeExecutor.decideKeepIncrementalState(
-        /*batch=*/ false,
-        /*keepStateAfterBuild=*/ true,
-        /*shouldTrackIncrementalState=*/ false,
-        /*discardAnalysisCache=*/ false,
+        /* batch= */ false,
+        /* keepStateAfterBuild= */ true,
+        /* shouldTrackIncrementalState= */ false,
+        /* heuristicallyDropNodes= */ false,
+        /* discardAnalysisCache= */ false,
         reporter);
     skyframeExecutor.setActive(true);
     syncSkyframeExecutor();
@@ -2140,10 +2141,11 @@
     options.parse("--keep_going", "--jobs=1", "--discard_analysis_cache");
     skyframeExecutor.setActive(false);
     skyframeExecutor.decideKeepIncrementalState(
-        /*batch=*/ true,
-        /*keepStateAfterBuild=*/ true,
-        /*shouldTrackIncrementalState=*/ true,
-        /*discardAnalysisCache=*/ true,
+        /* batch= */ true,
+        /* keepStateAfterBuild= */ true,
+        /* shouldTrackIncrementalState= */ true,
+        /* heuristicallyDropNodes= */ false,
+        /* discardAnalysisCache= */ true,
         reporter);
     skyframeExecutor.setActive(true);
     runCatastropheHaltsBuild();
@@ -2538,10 +2540,11 @@
 
     skyframeExecutor.setActive(false);
     skyframeExecutor.decideKeepIncrementalState(
-        /*batch=*/ false,
-        /*keepStateAfterBuild=*/ true,
+        /* batch= */ false,
+        /* keepStateAfterBuild= */ true,
         trackIncrementalState,
-        /*discardAnalysisCache=*/ false,
+        /* heuristicallyDropNodes= */ false,
+        /* discardAnalysisCache= */ false,
         reporter);
     skyframeExecutor.setActive(true);
     syncSkyframeExecutor();