Remove BuildingState, since it only has one field. Instead, keep the signaledDeps field directly in InMemoryNodeEntry.

Should save ~24 bytes per freshly evaluating node entry (I haven't calculated object alignment for InMemoryNodeEntry now, so could be more or less). Also might save some memory for re-evaluating node entries, since the BuildingState class had to be padded out to a multiple of 8 bytes before the DirtyBuildingState fields could start. Don't actually know if that was happening.

--
PiperOrigin-RevId: 151138224
MOS_MIGRATED_REVID=151138224
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
index 7f83301..7ad9025 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
@@ -39,12 +39,33 @@
  * say b depends on a. If a completes first, it's done. If it completes second, it needs to signal
  * b, and potentially re-schedule it. If b completes first, it must exit, because it will be
  * signaled (and re-scheduled) by a. If it completes second, it must signal (and re-schedule)
- * itself. However, if the Evaluator supported re-entrancy for a node, then this wouldn't have to
- * be so strict, because duplicate scheduling would be less problematic.
+ * itself. However, if the Evaluator supported re-entrancy for a node, then this wouldn't have to be
+ * so strict, because duplicate scheduling would be less problematic.
  *
- * <p>The transient state of an {@code InMemoryNodeEntry} is kept in a {@link BuildingState} object.
- * Many of the methods of {@code InMemoryNodeEntry} are just wrappers around the corresponding
- * {@link BuildingState} methods.
+ * <p>During its life, a node can go through states as follows:
+ *
+ * <ol>
+ *   <li>Non-existent
+ *   <li>Just created ({@link #isEvaluating} is false)
+ *   <li>Evaluating ({@link #isEvaluating} is true)
+ *   <li>Done ({@link #isDone} is true)
+ *   <li>Just created (when it is dirtied: {@link #dirtyBuildingState} is not null)
+ *   <li>Reset (just before it is re-evaluated: {@link #dirtyBuildingState#getDirtyState} returns
+ *       {@link DirtyState#NEEDS_REBUILDING})
+ *   <li>Evaluating
+ *   <li>Done
+ * </ol>
+ *
+ * <p>The "just created" state is there to allow the {@link EvaluableGraph#createIfAbsentBatch} and
+ * {@link NodeEntry#addReverseDepAndCheckIfDone} methods to be separate. All callers have to call
+ * both methods in that order if they want to create a node. The second method transitions the
+ * current node to the "evaluating" state and returns true only the first time it was called. A
+ * caller that gets "true" back from that call must start the evaluation of this node, while any
+ * subsequent callers must not.
+ *
+ * <p>An entry is set to "evaluating" as soon as it is scheduled for evaluation. Thus, even a node
+ * that is never actually built (for instance, a dirty node that is verified as clean) is in the
+ * "evaluating" state until it is done.
  *
  * <p>This class is public only for the benefit of alternative graph implementations outside of the
  * package.
@@ -117,10 +138,35 @@
   private List<Object> reverseDepsDataToConsolidate = null;
 
   /**
-   * The transient state of this entry, after it has been created but before it is done. It allows
-   * us to keep the current state of the entry across invalidation and successive evaluations.
+   * Object encapsulating dirty state of the object between when it is marked dirty and
+   * re-evaluated.
    */
-  @VisibleForTesting @Nullable protected volatile BuildingState buildingState = new BuildingState();
+  @VisibleForTesting @Nullable protected volatile DirtyBuildingState dirtyBuildingState = null;
+
+  private static final int NOT_EVALUATING_SENTINEL = -1;
+
+  /**
+   * The number of dependencies that are known to be done in a {@link NodeEntry} if it is already
+   * evaluating, and a sentinel (-1) indicating that it has not yet started evaluating otherwise.
+   * There is a potential check-then-act race here during evaluation, so we need to make sure that
+   * when this is increased, we always check if the new value is equal to the number of required
+   * dependencies, and if so, we must re-schedule the node for evaluation.
+   *
+   * <p>There are two potential pitfalls here: 1) If multiple dependencies signal this node in close
+   * succession, this node should be scheduled exactly once. 2) If a thread is still working on this
+   * node, it should not be scheduled.
+   *
+   * <p>The first problem is solved by the {@link #signalDep} method, which also returns if the node
+   * needs to be re-scheduled, and ensures that only one thread gets a true return value.
+   *
+   * <p>The second problem is solved by first adding the newly discovered deps to a node's {@link
+   * #directDeps}, and then looping through the direct deps and registering this node as a reverse
+   * dependency. This ensures that the signaledDeps counter can only reach {@link
+   * #directDeps#numElements} on the very last iteration of the loop, i.e., the thread is not
+   * working on the node anymore. Note that this requires that there is no code after the loop in
+   * {@code ParallelEvaluator.Evaluate#run}.
+   */
+  private int signaledDeps = NOT_EVALUATING_SENTINEL;
 
   /**
    * Construct a InMemoryNodeEntry. Use ONLY in Skyframe evaluation and graph implementations.
@@ -135,7 +181,7 @@
 
   @Override
   public boolean isDone() {
-    return buildingState == null;
+    return value != null && !isEvaluating();
   }
 
   @Override
@@ -191,7 +237,7 @@
   }
 
   private DirtyBuildingState getDirtyBuildingState() {
-    return (DirtyBuildingState) Preconditions.checkNotNull(buildingState, this);
+    return Preconditions.checkNotNull(dirtyBuildingState, "Didn't have state: %s", this);
   }
 
   /**
@@ -199,7 +245,8 @@
    * need to override the other.
    */
   protected void markDone() {
-    buildingState = null;
+    dirtyBuildingState = null;
+    signaledDeps = NOT_EVALUATING_SENTINEL;
   }
 
   protected synchronized Set<SkyKey> setStateFinishedAndReturnReverseDepsToSignal() {
@@ -261,8 +308,11 @@
     if (isDone()) {
       return DependencyState.DONE;
     }
-    return buildingState.startEvaluating() ? DependencyState.NEEDS_SCHEDULING
-                                           : DependencyState.ALREADY_EVALUATING;
+    boolean result = !isEvaluating();
+    if (result) {
+      signaledDeps = 0;
+    }
+    return result ? DependencyState.NEEDS_SCHEDULING : DependencyState.ALREADY_EVALUATING;
   }
 
   /** Sets {@link #reverseDeps}. Does not alter {@link #reverseDepsDataToConsolidate}. */
@@ -360,19 +410,22 @@
   @Override
   public synchronized boolean signalDep(Version childVersion) {
     Preconditions.checkState(!isDone(), "Value must not be done in signalDep %s", this);
-    return buildingState.signalDep(
-        /*childChanged=*/ !childVersion.atMost(lastEvaluatedVersion),
-        getTemporaryDirectDeps().numElements());
+    Preconditions.checkState(isEvaluating(), this);
+    signaledDeps++;
+    if (isDirty()) {
+      dirtyBuildingState.signalDepInternal(!childVersion.atMost(lastEvaluatedVersion), isReady());
+    }
+    return isReady();
   }
 
   @Override
   public synchronized boolean isDirty() {
-    return !isDone() && buildingState.isDirty();
+    return !isDone() && dirtyBuildingState != null;
   }
 
   @Override
   public synchronized boolean isChanged() {
-    return !isDone() && buildingState.isChanged();
+    return !isDone() && dirtyBuildingState != null && dirtyBuildingState.isChanged();
   }
 
   /** Checks that a caller is not trying to access not-stored graph edges. */
@@ -384,7 +437,7 @@
   public synchronized MarkedDirtyResult markDirty(boolean isChanged) {
     assertKeepEdges();
     if (isDone()) {
-      buildingState =
+      dirtyBuildingState =
           DirtyBuildingState.create(isChanged, GroupedList.<SkyKey>create(directDeps), value);
       value = null;
       directDeps = null;
@@ -414,14 +467,13 @@
         "Direct deps must be the same as those found last build for node to be marked clean: %s",
         this);
     Preconditions.checkState(isDirty(), this);
-    Preconditions.checkState(!buildingState.isChanged(), "shouldn't be changed: %s", this);
+    Preconditions.checkState(!dirtyBuildingState.isChanged(), "shouldn't be changed: %s", this);
     return setStateFinishedAndReturnReverseDepsToSignal();
   }
 
   @Override
   public synchronized void forceRebuild() {
-    Preconditions.checkState(
-        getTemporaryDirectDeps().numElements() == getDirtyBuildingState().getSignaledDeps(), this);
+    Preconditions.checkState(getTemporaryDirectDeps().numElements() == signaledDeps, this);
     getDirtyBuildingState().forceChanged();
   }
 
@@ -430,9 +482,9 @@
     return lastChangedVersion;
   }
 
-  /** @see DirtyBuildingState#getDirtyState() */
   @Override
   public synchronized NodeEntry.DirtyState getDirtyState() {
+    Preconditions.checkState(isEvaluating(), "Not evaluating for dirty state? %s", this);
     return getDirtyBuildingState().getDirtyState();
   }
 
@@ -440,6 +492,7 @@
   @Override
   public synchronized Collection<SkyKey> getNextDirtyDirectDeps() throws InterruptedException {
     Preconditions.checkState(isReady(), this);
+    Preconditions.checkState(isEvaluating(), "Not evaluating during getNextDirty? %s", this);
     return getDirtyBuildingState().getNextDirtyDirectDeps();
   }
 
@@ -463,12 +516,12 @@
 
   @Override
   public synchronized Set<SkyKey> getAllRemainingDirtyDirectDeps() throws InterruptedException {
+    Preconditions.checkState(isEvaluating(), "Not evaluating for remaining dirty? %s", this);
     if (isDirty()) {
       Preconditions.checkState(
           getDirtyBuildingState().getDirtyState() == DirtyState.REBUILDING, this);
       return getDirtyBuildingState().getAllRemainingDirtyDirectDeps(/*preservePosition=*/ true);
     } else {
-      Preconditions.checkState(buildingState.isEvaluating(), this);
       return ImmutableSet.of();
     }
   }
@@ -521,7 +574,17 @@
   @Override
   public synchronized boolean isReady() {
     Preconditions.checkState(!isDone(), "can't be ready if done: %s", this);
-    return buildingState.isReady(getTemporaryDirectDeps().numElements());
+    return isReady(getTemporaryDirectDeps().numElements());
+  }
+
+  /** Returns whether all known children of this node have signaled that they are done. */
+  private boolean isReady(int numDirectDeps) {
+    Preconditions.checkState(signaledDeps <= numDirectDeps, "%s %s", numDirectDeps, this);
+    return signaledDeps == numDirectDeps;
+  }
+
+  private boolean isEvaluating() {
+    return signaledDeps > NOT_EVALUATING_SENTINEL;
   }
 
   @Override
@@ -532,8 +595,9 @@
         .add("lastChangedVersion", lastChangedVersion)
         .add("lastEvaluatedVersion", lastEvaluatedVersion)
         .add("directDeps", isDone() ? GroupedList.create(directDeps) : directDeps)
+        .add("signaledDeps", signaledDeps)
         .add("reverseDeps", ReverseDepsUtility.toString(this))
-        .add("buildingState", buildingState)
+        .add("dirtyBuildingState", dirtyBuildingState)
         .toString();
   }
 
@@ -551,7 +615,7 @@
     nodeEntry.lastEvaluatedVersion = this.lastEvaluatedVersion;
     ReverseDepsUtility.addReverseDeps(nodeEntry, ReverseDepsUtility.getReverseDeps(this));
     nodeEntry.directDeps = directDeps;
-    nodeEntry.buildingState = null;
+    nodeEntry.dirtyBuildingState = null;
     return nodeEntry;
   }
 }