Root: Rather than use @AutoCodec, use a handwritten ObjectCodec that allows us to optimize for the common case of a very popular Root.

Our serialization scheme is: We are magically given a likely-to-be-popular Root instance `p` and then whenever we would serialize a Root `r`, we canonicalize `r` to `p` if they are `r.equals(p)`.

The Root#fromPath and Root#absoluteRoot methods permit creation of Root instances that are equivalent, and these methods are unfortunately very widely used in the codebase. Therefore, a serialization scheme that tries to make use of Object identity is brittle in the face of future changes.

While I'm here, delete Root#getFingerprint. An earlier commit (https://github.com/bazelbuild/bazel/commit/ce6ad29c88084fb327856149a39e2771e544cfa5) made this dead code.

RELNOTES: None
PiperOrigin-RevId: 300826154
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/FsUtils.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/FsUtils.java
index e244098..7b1f1d7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/FsUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/FsUtils.java
@@ -25,15 +25,23 @@
 
   public static final FileSystem TEST_FILESYSTEM = new InMemoryFileSystem();
 
-  public static final RootedPath TEST_ROOT =
-      RootedPath.toRootedPath(
-          Root.fromPath(TEST_FILESYSTEM.getPath(PathFragment.create("/anywhere/at/all"))),
-          PathFragment.create("all/at/anywhere"));
+  private static final Root TEST_ROOT =
+      Root.fromPath(TEST_FILESYSTEM.getPath(PathFragment.create("/anywhere/at/all")));
+
+  public static final RootedPath TEST_ROOTED_PATH =
+      RootedPath.toRootedPath(TEST_ROOT, PathFragment.create("all/at/anywhere"));
 
   private FsUtils() {}
 
-  /** Returns path relative to {@link #TEST_ROOT}. */
+  /** Returns path relative to {@link #TEST_ROOTED_PATH}. */
   public static PathFragment rootPathRelative(String path) {
-    return TEST_ROOT.getRootRelativePath().getRelative(path);
+    return TEST_ROOTED_PATH.getRootRelativePath().getRelative(path);
+  }
+
+  public static void addDependencies(SerializationTester tester) {
+    tester.addDependency(FileSystem.class, TEST_FILESYSTEM);
+    tester.addDependency(
+        Root.RootCodecDependencies.class,
+        new Root.RootCodecDependencies(/*likelyPopularRoot=*/ TEST_ROOT));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Root.java b/src/main/java/com/google/devtools/build/lib/vfs/Root.java
index 8d7f16d..86ba657 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Root.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Root.java
@@ -15,13 +15,16 @@
 
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
 import javax.annotation.Nullable;
 
 /**
@@ -66,8 +69,6 @@
   /** Returns whether the given absolute path fragment is under this root. */
   public abstract boolean contains(PathFragment absolutePathFragment);
 
-  public abstract BigInteger getFingerprint();
-
   /**
    * Returns the underlying path. Please avoid using this method.
    *
@@ -79,20 +80,11 @@
   public abstract boolean isAbsolute();
 
   /** Implementation of Root that is backed by a {@link Path}. */
-  @AutoCodec
   public static final class PathRoot extends Root {
     private final Path path;
-    private final BigInteger fingerprint;
 
-    PathRoot(Path path) {
+    private PathRoot(Path path) {
       this.path = path;
-      // Can't use BigIntegerFingerprint because would cause cycle.
-      this.fingerprint =
-          new BigInteger(
-              1,
-              DigestHashFunction.SHA256
-                  .cloneOrCreateMessageDigest()
-                  .digest(path.getPathString().getBytes(StandardCharsets.UTF_8)));
     }
 
     @Override
@@ -154,11 +146,6 @@
     }
 
     @Override
-    public BigInteger getFingerprint() {
-      return fingerprint;
-    }
-
-    @Override
     public boolean equals(Object o) {
       if (this == o) {
         return true;
@@ -177,10 +164,7 @@
   }
 
   /** An absolute root of a file system. Can only resolve absolute path fragments. */
-  @AutoCodec
   public static final class AbsoluteRoot extends Root {
-    private static final BigInteger FINGERPRINT = new BigInteger("15742446659214128006");
-
     private FileSystem fileSystem; // Non-final for serialization
 
     AbsoluteRoot(FileSystem fileSystem) {
@@ -246,11 +230,6 @@
     }
 
     @Override
-    public BigInteger getFingerprint() {
-      return FINGERPRINT;
-    }
-
-    @Override
     public boolean equals(Object o) {
       if (this == o) {
         return true;
@@ -281,4 +260,65 @@
           Path.getFileSystemForSerialization());
     }
   }
+
+  /** Serialization dependencies for {@link RootCodec}. */
+  public static class RootCodecDependencies {
+    private final Root likelyPopularRoot;
+
+    public RootCodecDependencies(Root likelyPopularRoot) {
+      this.likelyPopularRoot = likelyPopularRoot;
+    }
+  }
+
+  @SuppressWarnings("unused") // Used at run-time via classpath scanning + reflection.
+  private static class RootCodec implements ObjectCodec<Root> {
+    @Override
+    public Class<? extends Root> getEncodedClass() {
+      return Root.class;
+    }
+
+    @Override
+    public void serialize(SerializationContext context, Root root, CodedOutputStream codedOut)
+        throws SerializationException, IOException {
+      RootCodecDependencies codecDeps = context.getDependency(RootCodecDependencies.class);
+      if (root.equals(codecDeps.likelyPopularRoot)) {
+        codedOut.writeBoolNoTag(true);
+        return;
+      }
+
+      codedOut.writeBoolNoTag(false);
+      if (root instanceof PathRoot) {
+        codedOut.writeBoolNoTag(true);
+        PathRoot pathRoot = (PathRoot) root;
+        context.serialize(pathRoot.path, codedOut);
+        return;
+      }
+
+      if (root instanceof AbsoluteRoot) {
+        codedOut.writeBoolNoTag(false);
+        AbsoluteRoot absoluteRoot = (AbsoluteRoot) root;
+        context.serialize(absoluteRoot.fileSystem, codedOut);
+        return;
+      }
+
+      throw new IllegalStateException("Unexpected Root: " + root);
+    }
+
+    @Override
+    public Root deserialize(DeserializationContext context, CodedInputStream codedIn)
+        throws SerializationException, IOException {
+      if (codedIn.readBool()) {
+        RootCodecDependencies codecDeps = context.getDependency(RootCodecDependencies.class);
+        return codecDeps.likelyPopularRoot;
+      }
+
+      if (codedIn.readBool()) {
+        Path path = context.deserialize(codedIn);
+        return new PathRoot(path);
+      }
+
+      FileSystem fileSystem = context.deserialize(codedIn);
+      return new AbsoluteRoot(fileSystem);
+    }
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index 0e6f142..013d4bc 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -263,6 +263,8 @@
     anotherArtifact.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA);
     new SerializationTester(artifact, anotherArtifact)
         .addDependency(FileSystem.class, scratch.getFileSystem())
+        .addDependency(
+            Root.RootCodecDependencies.class, new Root.RootCodecDependencies(anotherRoot.getRoot()))
         .runTests();
   }
 
@@ -282,9 +284,13 @@
                 .addReferenceConstant(scratch.getFileSystem())
                 .setAllowDefaultCodec(true)
                 .build(),
-            ImmutableMap.of(
-                FileSystem.class, scratch.getFileSystem(),
-                ArtifactResolverSupplier.class, artifactResolverSupplierForTest));
+            ImmutableMap.<Class<?>, Object>builder()
+                .put(FileSystem.class, scratch.getFileSystem())
+                .put(ArtifactResolverSupplier.class, artifactResolverSupplierForTest)
+                .put(
+                    Root.RootCodecDependencies.class,
+                    new Root.RootCodecDependencies(artifactRoot.getRoot()))
+                .build());
 
     PathFragment pathFragment = PathFragment.create("src/foo.cc");
     ArtifactOwner owner = new LabelArtifactOwner(Label.parseAbsoluteUnchecked("//foo:bar"));
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
index 1715e15b..704a060 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.Root;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -137,6 +138,9 @@
     SymlinkAction action = SymlinkAction.toExecutable(NULL_ACTION_OWNER, input, output, "progress");
     new SerializationTester(action)
         .addDependency(FileSystem.class, scratch.getFileSystem())
+        .addDependency(
+            Root.RootCodecDependencies.class,
+            new Root.RootCodecDependencies(Root.absoluteRoot(scratch.getFileSystem())))
         .setVerificationFunction(
             (in, out) -> {
               SymlinkAction inAction = (SymlinkAction) in;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
index c8389b1..4c5b88a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
@@ -34,6 +34,7 @@
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.Root;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -110,6 +111,7 @@
   public void testCodec() throws Exception {
     new SerializationTester(action)
         .addDependency(FileSystem.class, scratch.getFileSystem())
+        .addDependency(Root.RootCodecDependencies.class, new Root.RootCodecDependencies(root))
         .addDependencies(SerializationDepsUtils.SERIALIZATION_DEPS_FOR_TEST)
         .setVerificationFunction(
             (in, out) -> {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index b96d5c1..597f7a9 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -289,6 +289,9 @@
             true);
     new SerializationTester(actionExecutionValue)
         .addDependency(FileSystem.class, root.getFileSystem())
+        .addDependency(
+            Root.RootCodecDependencies.class,
+            new Root.RootCodecDependencies(Root.absoluteRoot(root.getFileSystem())))
         .runTests();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryCodecTest.java
index 0472323..94a49a1 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/CollectPackagesUnderDirectoryCodecTest.java
@@ -20,7 +20,6 @@
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.TestUtils;
-import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.lib.vfs.RootedPath;
@@ -34,7 +33,8 @@
 
   @Test
   public void testCodec() throws Exception {
-    new SerializationTester(
+    SerializationTester serializationTester =
+        new SerializationTester(
             NoErrorCollectPackagesUnderDirectoryValue.EMPTY,
             CollectPackagesUnderDirectoryValue.ofNoError(
                 true,
@@ -50,9 +50,9 @@
                 "my error message",
                 ImmutableMap.of(
                     rootedPath("/a", "b"), false,
-                    rootedPath("/c", "d"), true)))
-        .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
-        .runTests();
+                    rootedPath("/c", "d"), true)));
+    FsUtils.addDependencies(serializationTester);
+    serializationTester.runTests();
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileValueTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileValueTest.java
index fad42f0..56e4acc 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileValueTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileValueTest.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.actions.FileValue;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
-import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,28 +29,29 @@
 public class FileValueTest {
   @Test
   public void testCodec() throws Exception {
-    new SerializationTester(
+    SerializationTester serializationTester =
+        new SerializationTester(
             // Assume we have adequate coverage for FileStateValue serialization.
             new FileValue.RegularFileValue(
-                FsUtils.TEST_ROOT, FileStateValue.NONEXISTENT_FILE_STATE_NODE),
+                FsUtils.TEST_ROOTED_PATH, FileStateValue.NONEXISTENT_FILE_STATE_NODE),
             new FileValue.DifferentRealPathFileValueWithStoredChain(
-                FsUtils.TEST_ROOT,
+                FsUtils.TEST_ROOTED_PATH,
                 FileStateValue.DIRECTORY_FILE_STATE_NODE,
-                ImmutableList.of(FsUtils.TEST_ROOT)),
+                ImmutableList.of(FsUtils.TEST_ROOTED_PATH)),
             new FileValue.DifferentRealPathFileValueWithoutStoredChain(
-                FsUtils.TEST_ROOT, FileStateValue.DIRECTORY_FILE_STATE_NODE),
+                FsUtils.TEST_ROOTED_PATH, FileStateValue.DIRECTORY_FILE_STATE_NODE),
             new FileValue.SymlinkFileValueWithStoredChain(
-                FsUtils.TEST_ROOT,
+                FsUtils.TEST_ROOTED_PATH,
                 new FileStateValue.RegularFileStateValue(
                     /*size=*/ 100, /*digest=*/ new byte[] {1, 2, 3, 4, 5}, /*contentsProxy=*/ null),
-                ImmutableList.of(FsUtils.TEST_ROOT),
+                ImmutableList.of(FsUtils.TEST_ROOTED_PATH),
                 PathFragment.create("somewhere/else")),
             new FileValue.SymlinkFileValueWithoutStoredChain(
-                FsUtils.TEST_ROOT,
+                FsUtils.TEST_ROOTED_PATH,
                 new FileStateValue.RegularFileStateValue(
                     /*size=*/ 100, /*digest=*/ new byte[] {1, 2, 3, 4, 5}, /*contentsProxy=*/ null),
-                PathFragment.create("somewhere/else")))
-        .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
-        .runTests();
+                PathFragment.create("somewhere/else")));
+    FsUtils.addDependencies(serializationTester);
+    serializationTester.runTests();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/GlobDescriptorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/GlobDescriptorTest.java
index 3d2e4bd..7eac47f 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/GlobDescriptorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/GlobDescriptorTest.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
-import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Root;
 import org.junit.Test;
@@ -32,22 +31,23 @@
 
   @Test
   public void testSerialization() throws Exception {
-    new SerializationTester(
-            GlobDescriptor.create(
-                PackageIdentifier.create("@foo", PathFragment.create("//bar")),
-                Root.fromPath(FsUtils.TEST_FILESYSTEM.getPath("/packageRoot")),
-                PathFragment.create("subdir"),
-                "pattern",
-                /*excludeDirs=*/ false),
-            GlobDescriptor.create(
-                PackageIdentifier.create("@bar", PathFragment.create("//foo")),
-                Root.fromPath(FsUtils.TEST_FILESYSTEM.getPath("/anotherPackageRoot")),
-                PathFragment.create("anotherSubdir"),
-                "pattern",
-                /*excludeDirs=*/ true))
-        .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
-        .setVerificationFunction(GlobDescriptorTest::verifyEquivalent)
-        .runTests();
+    SerializationTester serializationTester =
+        new SerializationTester(
+                GlobDescriptor.create(
+                    PackageIdentifier.create("@foo", PathFragment.create("//bar")),
+                    Root.fromPath(FsUtils.TEST_FILESYSTEM.getPath("/packageRoot")),
+                    PathFragment.create("subdir"),
+                    "pattern",
+                    /*excludeDirs=*/ false),
+                GlobDescriptor.create(
+                    PackageIdentifier.create("@bar", PathFragment.create("//foo")),
+                    Root.fromPath(FsUtils.TEST_FILESYSTEM.getPath("/anotherPackageRoot")),
+                    PathFragment.create("anotherSubdir"),
+                    "pattern",
+                    /*excludeDirs=*/ true))
+            .setVerificationFunction(GlobDescriptorTest::verifyEquivalent);
+    FsUtils.addDependencies(serializationTester);
+    serializationTester.runTests();
   }
 
   private static void verifyEquivalent(GlobDescriptor orig, GlobDescriptor deserialized) {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
index 2c80f1d..523776d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.skyframe.PrepareDepsOfTargetsUnderDirectoryValue.PrepareDepsOfTargetsUnderDirectoryKey;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
-import com.google.devtools.build.lib.vfs.FileSystem;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -30,14 +29,16 @@
 
   @Test
   public void testCodec() throws Exception {
-    new SerializationTester(
+    SerializationTester serializationTester =
+        new SerializationTester(
             PrepareDepsOfTargetsUnderDirectoryKey.create(
                 new RecursivePkgKey(
                     RepositoryName.MAIN,
-                    FsUtils.TEST_ROOT,
+                    FsUtils.TEST_ROOTED_PATH,
                     ImmutableSet.of(FsUtils.rootPathRelative("here"))),
-                FilteringPolicies.and(FilteringPolicies.NO_FILTER, FilteringPolicies.FILTER_TESTS)))
-        .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
-        .runTests();
+                FilteringPolicies.and(
+                    FilteringPolicies.NO_FILTER, FilteringPolicies.FILTER_TESTS)));
+    FsUtils.addDependencies(serializationTester);
+    serializationTester.runTests();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupKeyCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupKeyCodecTest.java
index 0e84bb9..1be78b9 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupKeyCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupKeyCodecTest.java
@@ -18,7 +18,6 @@
 import com.google.devtools.build.lib.skyframe.SkylarkImportLookupValue.SkylarkImportLookupKey;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
-import com.google.devtools.build.lib.vfs.FileSystem;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -29,7 +28,8 @@
 
   @Test
   public void testCodec() throws Exception {
-    new SerializationTester(
+    SerializationTester serializationTester =
+        new SerializationTester(
             SkylarkImportLookupKey.create(
                 Label.parseAbsolute("//foo/bar:baz", ImmutableMap.of()), false, -1, null),
             SkylarkImportLookupKey.create(
@@ -40,8 +40,8 @@
                 Label.parseAbsolute("//foo/bar:baz", ImmutableMap.of()),
                 true,
                 4,
-                FsUtils.TEST_ROOT))
-        .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
-        .runTests();
+                FsUtils.TEST_ROOTED_PATH));
+    FsUtils.addDependencies(serializationTester);
+    serializationTester.runTests();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index afe1d43..1c73180 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -65,6 +65,7 @@
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.lib.vfs.Symlinks;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
@@ -131,6 +132,9 @@
     parent.setGeneratingActionKey(ActionLookupData.create(ACTION_LOOKUP_KEY, 0));
     new SerializationTester(parent, ActionInputHelper.treeFileArtifact(parent, "child"))
         .addDependency(FileSystem.class, scratch.getFileSystem())
+        .addDependency(
+            Root.RootCodecDependencies.class,
+            new Root.RootCodecDependencies(Root.absoluteRoot(scratch.getFileSystem())))
         .runTests();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/RootTest.java b/src/test/java/com/google/devtools/build/lib/vfs/RootTest.java
index 888ecfb..ace28cc 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/RootTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/RootTest.java
@@ -16,9 +16,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.testing.EqualsTester;
 import com.google.devtools.build.lib.clock.BlazeClock;
+import com.google.devtools.build.lib.skyframe.serialization.AutoRegistry;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecRegistry;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecs;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
 import java.util.Comparator;
@@ -104,9 +108,56 @@
   }
 
   @Test
-  public void testSerialization() throws Exception {
-    new SerializationTester(Root.absoluteRoot(fs), Root.fromPath(fs.getPath("/foo")))
+  public void testSerialization_Simple() throws Exception {
+    Root fooPathRoot = Root.fromPath(fs.getPath("/foo"));
+    Root barPathRoot = Root.fromPath(fs.getPath("/bar"));
+    new SerializationTester(Root.absoluteRoot(fs), fooPathRoot, barPathRoot)
         .addDependency(FileSystem.class, fs)
+        .addDependency(
+            Root.RootCodecDependencies.class,
+            new Root.RootCodecDependencies(/*likelyPopularRoot=*/ fooPathRoot))
         .runTests();
   }
+
+  @Test
+  public void testSerialization_LikelyPopularRootIsCanonicalized() throws Exception {
+    Root fooPathRoot = Root.fromPath(fs.getPath("/foo"));
+    Root otherFooPathRoot = Root.fromPath(fs.getPath("/foo"));
+    Root barPathRoot = Root.fromPath(fs.getPath("/bar"));
+    Root fsAabsoluteRoot = Root.absoluteRoot(fs);
+
+    assertThat(fooPathRoot).isNotSameInstanceAs(otherFooPathRoot);
+    assertThat(fooPathRoot).isEqualTo(otherFooPathRoot);
+
+    ObjectCodecRegistry registry = AutoRegistry.get();
+    ImmutableMap<Class<?>, Object> dependencies =
+        ImmutableMap.<Class<?>, Object>builder()
+            .put(FileSystem.class, fs)
+            .put(
+                Root.RootCodecDependencies.class,
+                new Root.RootCodecDependencies(/*likelyPopularRoot=*/ fooPathRoot))
+            .build();
+    ObjectCodecRegistry.Builder registryBuilder = registry.getBuilder();
+    for (Object val : dependencies.values()) {
+      registryBuilder.addReferenceConstant(val);
+    }
+    ObjectCodecs objectCodecs = new ObjectCodecs(registryBuilder.build(), dependencies);
+
+    Root fooPathRootDeserialized =
+        (Root) objectCodecs.deserialize(objectCodecs.serialize(fooPathRoot));
+    Root otherFooPathRootDeserialized =
+        (Root) objectCodecs.deserialize(objectCodecs.serialize(otherFooPathRoot));
+    assertThat(fooPathRootDeserialized).isSameInstanceAs(fooPathRoot);
+    assertThat(otherFooPathRootDeserialized).isSameInstanceAs(fooPathRoot);
+
+    Root barPathRootDeserialized =
+        (Root) objectCodecs.deserialize(objectCodecs.serialize(barPathRoot));
+    assertThat(barPathRootDeserialized).isNotSameInstanceAs(barPathRoot);
+    assertThat(barPathRootDeserialized).isEqualTo(barPathRoot);
+
+    Root fsAabsoluteRootDeserialized =
+        (Root) objectCodecs.deserialize(objectCodecs.serialize(fsAabsoluteRoot));
+    assertThat(fsAabsoluteRootDeserialized).isNotSameInstanceAs(fsAabsoluteRoot);
+    assertThat(fsAabsoluteRootDeserialized).isEqualTo(fsAabsoluteRoot);
+  }
 }