Integrates CodecScanner into SkyValueEncoder.

* Deletes a number of CODEC references, now superceded by registry functionality.
* Minimal SkyKeySerializer modifications for correctness, as certain codec changes require
codecs to be in available in the registry.

PiperOrigin-RevId: 186834520
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
index 404051e..e914396 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
@@ -37,10 +37,8 @@
   private FilteringPolicies() {
   }
 
-  /**
-   * Base class for singleton filtering policies.
-   */
-  private abstract static class AbstractFilteringPolicy implements FilteringPolicy {
+  /** Base class for singleton filtering policies. */
+  private abstract static class AbstractFilteringPolicy extends FilteringPolicy {
     private final int hashCode = getClass().getSimpleName().hashCode();
 
     @Override
@@ -90,7 +88,7 @@
   }
 
   /** FilteringPolicy for combining FilteringPolicies. */
-  public static class AndFilteringPolicy implements FilteringPolicy {
+  public static class AndFilteringPolicy extends FilteringPolicy {
     private final FilteringPolicy firstPolicy;
     private final FilteringPolicy secondPolicy;
 
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicy.java b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicy.java
index 9e98190..3790fbc 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicy.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicy.java
@@ -15,21 +15,20 @@
 
 import com.google.devtools.build.lib.concurrent.ThreadSafety;
 import com.google.devtools.build.lib.packages.Target;
-
 import java.io.Serializable;
 
 /**
  * A filtering policy defines how target patterns are matched. For instance, we may wish to select
  * only tests or no tests.
  */
-public interface FilteringPolicy extends Serializable {
+public abstract class FilteringPolicy implements Serializable {
 
   /**
    * Returns true if this target should be retained.
    *
    * @param explicit true iff the label was specified explicitly, as opposed to being discovered by
-   *                 a wildcard.
+   *     a wildcard.
    */
   @ThreadSafety.ThreadSafe
-  boolean shouldRetain(Target target, boolean explicit);
+  public abstract boolean shouldRetain(Target target, boolean explicit);
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BlacklistedPackagePrefixesValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/BlacklistedPackagePrefixesValue.java
index 0bdd5c5..73d13b8 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BlacklistedPackagePrefixesValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BlacklistedPackagePrefixesValue.java
@@ -15,22 +15,22 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.LegacySkyKey;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 
-/**
- * An immutable set of package name prefixes that should be blacklisted.
- */
+/** An immutable set of package name prefixes that should be blacklisted. */
+@AutoCodec
 public class BlacklistedPackagePrefixesValue implements SkyValue {
   private final ImmutableSet<PathFragment> patterns;
 
   private static final SkyKey BLACKLIST_KEY =
       LegacySkyKey.create(SkyFunctions.BLACKLISTED_PACKAGE_PREFIXES, "");
 
-  public BlacklistedPackagePrefixesValue(ImmutableSet<PathFragment> exclusions) {
-    this.patterns = Preconditions.checkNotNull(exclusions);
+  public BlacklistedPackagePrefixesValue(ImmutableSet<PathFragment> patterns) {
+    this.patterns = Preconditions.checkNotNull(patterns);
   }
 
   public static SkyKey key() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
index 491275c..4454dbe 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupValue.java
@@ -15,6 +15,7 @@
 
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.LegacySkyKey;
 import com.google.devtools.build.skyframe.SkyKey;
@@ -27,7 +28,7 @@
  */
 public abstract class ContainingPackageLookupValue implements SkyValue {
 
-  public static final NoContainingPackage NONE = new NoContainingPackage();
+  public static final NoContainingPackage NONE = NoContainingPackage.INSTANCE;
 
   /** Returns whether there is a containing package. */
   public abstract boolean hasContainingPackage();
@@ -49,7 +50,9 @@
   }
 
   /** Value indicating there is no containing package. */
+  @AutoCodec(strategy = AutoCodec.Strategy.SINGLETON)
   public static class NoContainingPackage extends ContainingPackageLookupValue {
+    public static final NoContainingPackage INSTANCE = new NoContainingPackage();
 
     private NoContainingPackage() {}
 
@@ -70,12 +73,15 @@
   }
 
   /** A successful lookup value. */
+  @AutoCodec
   public static class ContainingPackage extends ContainingPackageLookupValue {
     private final PackageIdentifier containingPackage;
     private final Root containingPackageRoot;
 
-    private ContainingPackage(PackageIdentifier pkgId, Root containingPackageRoot) {
-      this.containingPackage = pkgId;
+    @AutoCodec.Instantiator
+    @AutoCodec.VisibleForSerialization
+    ContainingPackage(PackageIdentifier containingPackage, Root containingPackageRoot) {
+      this.containingPackage = containingPackage;
       this.containingPackageRoot = containingPackageRoot;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupValue.java
index 408bc27..0c78c40 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupValue.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.packages.BuildFileName;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.lib.vfs.RootedPath;
@@ -38,11 +39,11 @@
 public abstract class PackageLookupValue implements SkyValue {
 
   public static final NoBuildFilePackageLookupValue NO_BUILD_FILE_VALUE =
-      new NoBuildFilePackageLookupValue();
+      NoBuildFilePackageLookupValue.INSTANCE;
   public static final DeletedPackageLookupValue DELETED_PACKAGE_VALUE =
-      new DeletedPackageLookupValue();
+      DeletedPackageLookupValue.INSTANCE;
   public static final NoRepositoryPackageLookupValue NO_SUCH_REPOSITORY_VALUE =
-      new NoRepositoryPackageLookupValue();
+      NoRepositoryPackageLookupValue.INSTANCE;
 
   enum ErrorReason {
     /** There is no BUILD file. */
@@ -118,12 +119,15 @@
   }
 
   /** Successful lookup value. */
+  @AutoCodec
   public static class SuccessfulPackageLookupValue extends PackageLookupValue {
 
     private final Root root;
     private final BuildFileName buildFileName;
 
-    private SuccessfulPackageLookupValue(Root root, BuildFileName buildFileName) {
+    @AutoCodec.Instantiator
+    @AutoCodec.VisibleForSerialization
+    SuccessfulPackageLookupValue(Root root, BuildFileName buildFileName) {
       this.root = root;
       this.buildFileName = buildFileName;
     }
@@ -187,7 +191,9 @@
   }
 
   /** Marker value for no build file found. */
+  @AutoCodec(strategy = AutoCodec.Strategy.SINGLETON)
   public static class NoBuildFilePackageLookupValue extends UnsuccessfulPackageLookupValue {
+    static final NoBuildFilePackageLookupValue INSTANCE = new NoBuildFilePackageLookupValue();
 
     private NoBuildFilePackageLookupValue() {
     }
@@ -204,11 +210,14 @@
   }
 
   /** Value indicating the package name was in error. */
+  @AutoCodec
   public static class InvalidNamePackageLookupValue extends UnsuccessfulPackageLookupValue {
 
     private final String errorMsg;
 
-    private InvalidNamePackageLookupValue(String errorMsg) {
+    @AutoCodec.Instantiator
+    @AutoCodec.VisibleForSerialization
+    InvalidNamePackageLookupValue(String errorMsg) {
       this.errorMsg = errorMsg;
     }
 
@@ -243,13 +252,16 @@
   }
 
   /** Value indicating the package name was in error. */
+  @AutoCodec
   public static class IncorrectRepositoryReferencePackageLookupValue
       extends UnsuccessfulPackageLookupValue {
 
     private final PackageIdentifier invalidPackageIdentifier;
     private final PackageIdentifier correctedPackageIdentifier;
 
-    private IncorrectRepositoryReferencePackageLookupValue(
+    @AutoCodec.Instantiator
+    @AutoCodec.VisibleForSerialization
+    IncorrectRepositoryReferencePackageLookupValue(
         PackageIdentifier invalidPackageIdentifier, PackageIdentifier correctedPackageIdentifier) {
       this.invalidPackageIdentifier = invalidPackageIdentifier;
       this.correctedPackageIdentifier = correctedPackageIdentifier;
@@ -305,7 +317,9 @@
   }
 
   /** Marker value for a deleted package. */
+  @AutoCodec(strategy = AutoCodec.Strategy.SINGLETON)
   public static class DeletedPackageLookupValue extends UnsuccessfulPackageLookupValue {
+    static final DeletedPackageLookupValue INSTANCE = new DeletedPackageLookupValue();
 
     private DeletedPackageLookupValue() {
     }
@@ -325,7 +339,9 @@
    * Marker value for repository we could not find. This can happen when looking for a label that
    * specifies a non-existent repository.
    */
+  @AutoCodec(strategy = AutoCodec.Strategy.SINGLETON)
   public static class NoRepositoryPackageLookupValue extends UnsuccessfulPackageLookupValue {
+    static final NoRepositoryPackageLookupValue INSTANCE = new NoRepositoryPackageLookupValue();
 
     private NoRepositoryPackageLookupValue() {}
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
index 8f5277a..2f83389 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.packages.RuleVisibility;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ConflictException;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.syntax.SkylarkSemantics;
 import com.google.devtools.build.skyframe.Injectable;
 import com.google.devtools.build.skyframe.LegacySkyKey;
@@ -40,6 +41,7 @@
  * "precomputed" from skyframe's perspective and so the graph needs to be prepopulated with them
  * (e.g. via injection).
  */
+@AutoCodec
 public final class PrecomputedValue implements SkyValue {
   /**
    * An externally-injected precomputed value. Exists so that modules can inject precomputed values
@@ -103,6 +105,7 @@
 
   private final Object value;
 
+  @AutoCodec.Instantiator
   public PrecomputedValue(Object value) {
     this.value = Preconditions.checkNotNull(value);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValueCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValueCodec.java
deleted file mode 100644
index 1fc108d..0000000
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValueCodec.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2017 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.skyframe;
-
-import com.google.common.base.Preconditions;
-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.ObjectCodecs;
-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.util.function.Supplier;
-
-/**
- * {@link ObjectCodec} for {@link PrecomputedValue} objects. Because {@link PrecomputedValue}
- * objects can theoretically contain any kind of value object as their value, an {@link
- * ObjectCodecs} instance must be accessible to this codec during serialization/deserialization, so
- * that it can handle the arbitrary objects it's given.
- */
-public class PrecomputedValueCodec implements ObjectCodec<PrecomputedValue> {
-  private final Supplier<ObjectCodecs> objectCodecsSupplier;
-
-  public PrecomputedValueCodec(Supplier<ObjectCodecs> objectCodecsSupplier) {
-    this.objectCodecsSupplier = objectCodecsSupplier;
-  }
-
-  @Override
-  public Class<PrecomputedValue> getEncodedClass() {
-    return PrecomputedValue.class;
-  }
-
-  @Override
-  public void serialize(
-      SerializationContext context, PrecomputedValue obj, CodedOutputStream codedOut)
-      throws SerializationException, IOException {
-    ObjectCodecs objectCodecs = objectCodecsSupplier.get();
-    Object val = obj.get();
-    Preconditions.checkState(!(val instanceof PrecomputedValue), "recursive precomputed: %s", obj);
-    // TODO(janakr): this assumes the classifier is the class of the object. This should be enforced
-    // by the ObjectCodecs instance.
-    String classifier = val.getClass().getName();
-    codedOut.writeStringNoTag(classifier);
-    objectCodecs.serialize(classifier, val, codedOut);
-  }
-
-  @Override
-  public PrecomputedValue deserialize(DeserializationContext context, CodedInputStream codedIn)
-      throws SerializationException, IOException {
-    ObjectCodecs objectCodecs = objectCodecsSupplier.get();
-    Object val = objectCodecs.deserialize(codedIn.readBytes(), codedIn);
-    return new PrecomputedValue(val);
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsValue.java
index ad0ed0c..6c91565 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsValue.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
 import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternKey;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.skyframe.LegacySkyKey;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
@@ -26,20 +27,19 @@
 import java.util.Objects;
 
 /**
- * The value returned by {@link PrepareDepsOfPatternsFunction}. Although that function is
- * invoked primarily for its side effect (i.e. ensuring the graph contains targets matching the
- * pattern sequence and their transitive dependencies), this value contains the
- * {@link TargetPatternKey} arguments of the {@link PrepareDepsOfPatternFunction}s evaluated in
- * service of it.
+ * The value returned by {@link PrepareDepsOfPatternsFunction}. Although that function is invoked
+ * primarily for its side effect (i.e. ensuring the graph contains targets matching the pattern
+ * sequence and their transitive dependencies), this value contains the {@link TargetPatternKey}
+ * arguments of the {@link PrepareDepsOfPatternFunction}s evaluated in service of it.
  *
  * <p>Because the returned value may remain the same when the side-effects of this function
- * evaluation change, this value and the {@link PrepareDepsOfPatternsFunction} which computes it
- * are incompatible with change pruning. It should only be requested by consumers who do not
- * require reevaluation when {@link PrepareDepsOfPatternsFunction} is reevaluated. Safe consumers
- * include, e.g., top-level consumers, and other functions which invoke {@link
- * PrepareDepsOfPatternsFunction} solely for its side-effects and which do not behave differently
- * depending on those side-effects.
+ * evaluation change, this value and the {@link PrepareDepsOfPatternsFunction} which computes it are
+ * incompatible with change pruning. It should only be requested by consumers who do not require
+ * reevaluation when {@link PrepareDepsOfPatternsFunction} is reevaluated. Safe consumers include,
+ * e.g., top-level consumers, and other functions which invoke {@link PrepareDepsOfPatternsFunction}
+ * solely for its side-effects and which do not behave differently depending on those side-effects.
  */
+@AutoCodec
 @Immutable
 @ThreadSafe
 public final class PrepareDepsOfPatternsValue implements SkyValue {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryValue.java
index 54db4d5..4c948a7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryValue.java
@@ -21,6 +21,8 @@
 import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
 import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
 import com.google.devtools.build.lib.skyframe.RecursivePkgValue.RecursivePkgKey;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.LegacySkyKey;
@@ -70,12 +72,17 @@
   /**
    * The argument value for {@link SkyKey}s of {@link PrepareDepsOfTargetsUnderDirectoryFunction}.
    */
+  @AutoCodec
   public static final class PrepareDepsOfTargetsUnderDirectoryKey implements Serializable {
+    public static final ObjectCodec<PrepareDepsOfTargetsUnderDirectoryKey> CODEC =
+        new PrepareDepsOfTargetsUnderDirectoryValue_PrepareDepsOfTargetsUnderDirectoryKey_AutoCodec();
+
     private final RecursivePkgKey recursivePkgKey;
     private final FilteringPolicy filteringPolicy;
 
-    public PrepareDepsOfTargetsUnderDirectoryKey(RecursivePkgKey recursivePkgKey,
-        FilteringPolicy filteringPolicy) {
+    @AutoCodec.Instantiator
+    public PrepareDepsOfTargetsUnderDirectoryKey(
+        RecursivePkgKey recursivePkgKey, FilteringPolicy filteringPolicy) {
       this.recursivePkgKey = Preconditions.checkNotNull(recursivePkgKey);
       this.filteringPolicy = Preconditions.checkNotNull(filteringPolicy);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePkgValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePkgValue.java
index 74f94bf..59b1201 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePkgValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePkgValue.java
@@ -21,6 +21,8 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.LegacySkyKey;
@@ -69,19 +71,26 @@
   /**
    * A RecursivePkgKey is a tuple of a {@link RootedPath}, {@code rootedPath}, defining the
    * directory to recurse beneath in search of packages, and an {@link ImmutableSet} of {@link
-   * PathFragment}s, {@code excludedPaths}, relative to {@code rootedPath.getRoot}, defining the
-   * set of subdirectories strictly beneath {@code rootedPath} to skip.
+   * PathFragment}s, {@code excludedPaths}, relative to {@code rootedPath.getRoot}, defining the set
+   * of subdirectories strictly beneath {@code rootedPath} to skip.
    *
-   * <p>Throws {@link IllegalArgumentException} if {@code excludedPaths} contains any paths that
-   * are equal to {@code rootedPath} or that are not beneath {@code rootedPath}.
+   * <p>Throws {@link IllegalArgumentException} if {@code excludedPaths} contains any paths that are
+   * equal to {@code rootedPath} or that are not beneath {@code rootedPath}.
    */
+  @AutoCodec
   @ThreadSafe
   public static final class RecursivePkgKey implements Serializable {
+    public static final ObjectCodec<RecursivePkgKey> CODEC =
+        new RecursivePkgValue_RecursivePkgKey_AutoCodec();
+
     private final RepositoryName repositoryName;
     private final RootedPath rootedPath;
     private final ImmutableSet<PathFragment> excludedPaths;
 
-    public RecursivePkgKey(RepositoryName repositoryName, RootedPath rootedPath,
+    @AutoCodec.Instantiator
+    public RecursivePkgKey(
+        RepositoryName repositoryName,
+        RootedPath rootedPath,
         ImmutableSet<PathFragment> excludedPaths) {
       PathFragment.checkAllPathsAreUnder(excludedPaths, rootedPath.getRootRelativePath());
       Preconditions.checkState(!repositoryName.isDefault());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
index e404ab0..b1ff058 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
@@ -123,6 +123,20 @@
     }
   }
 
+  /**
+   * Creates a builder using the current contents of this registry.
+   *
+   * <p>This is much more efficient than scanning multiple times.
+   */
+  Builder getBuilder() {
+    Builder builder = newBuilder();
+    builder.setAllowDefaultCodec(defaultCodecDescriptor != null);
+    for (Map.Entry<String, CodecDescriptor> entry : stringMappedCodecs.entrySet()) {
+      builder.add(entry.getKey(), entry.getValue().getCodec());
+    }
+    return builder;
+  }
+
   /** Describes encoding logic. */
   static interface CodecDescriptor {
     void serialize(SerializationContext context, Object obj, CodedOutputStream codedOut)
@@ -143,11 +157,7 @@
      */
     int getTag();
 
-    /**
-     * Returns the underlying codec.
-     *
-     * <p>For backwards compatibility. New callers should prefer the methods above.
-     */
+    /** Returns the underlying codec. */
     ObjectCodec<?> getCodec();
   }
 
@@ -206,8 +216,6 @@
     private final ImmutableMap.Builder<String, CodecHolder> codecsBuilder = ImmutableMap.builder();
     private boolean allowDefaultCodec = true;
 
-    private Builder() {}
-
     /**
      * Add custom serialization strategy ({@code codec}) for {@code classifier}.
      *
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecs.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecs.java
index 99a02ae..4d31a03 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecs.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecs.java
@@ -38,8 +38,20 @@
    */
   ObjectCodecs(ObjectCodecRegistry codecRegistry, ImmutableMap<Class<?>, Object> dependencies) {
     this.codecRegistry = codecRegistry;
-    serializationContext = new SerializationContext(dependencies);
-    deserializationContext = new DeserializationContext(dependencies);
+    serializationContext = new SerializationContext(codecRegistry, dependencies);
+    deserializationContext = new DeserializationContext(codecRegistry, dependencies);
+  }
+
+  public ByteString serialize(Object subject) throws SerializationException {
+    ByteString.Output resultOut = ByteString.newOutput();
+    CodedOutputStream codedOut = CodedOutputStream.newInstance(resultOut);
+    try {
+      serializationContext.serialize(subject, codedOut);
+      codedOut.flush();
+      return resultOut.toByteString();
+    } catch (IOException e) {
+      throw new SerializationException("Failed to serialize " + subject, e);
+    }
   }
 
   /**
@@ -78,6 +90,15 @@
     }
   }
 
+  public Object deserialize(ByteString data) throws SerializationException {
+    CodedInputStream codedIn = data.newCodedInput();
+    try {
+      return deserializationContext.deserialize(codedIn);
+    } catch (IOException e) {
+      throw new SerializationException("Failed to deserialize data", e);
+    }
+  }
+
   /**
    * Deserialize {@code data} using the serialization strategy determined by {@code classifier}.
    * {@code classifier} should be the utf-8 encoded {@link ByteString} representation of the {@link
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java
index a0f5873..3d4988a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java
@@ -61,6 +61,7 @@
   private final DeserializationContext readContext;
   private final boolean skipBadDataTest;
   private final VerificationFunction<T> verificationFunction;
+  private final int repetitions;
 
   private ObjectCodecTester(
       ObjectCodec<T> underTest,
@@ -68,7 +69,8 @@
       SerializationContext writeContext,
       DeserializationContext readContext,
       boolean skipBadDataTest,
-      VerificationFunction<T> verificationFunction) {
+      VerificationFunction<T> verificationFunction,
+      int repetitions) {
     this.underTest = underTest;
     Preconditions.checkState(!subjects.isEmpty(), "No subjects provided");
     this.subjects = subjects;
@@ -76,6 +78,7 @@
     this.readContext = readContext;
     this.skipBadDataTest = skipBadDataTest;
     this.verificationFunction = verificationFunction;
+    this.repetitions = repetitions;
   }
 
   private void runTests() throws Exception {
@@ -90,11 +93,13 @@
   void testSerializeDeserialize() throws Exception {
     Stopwatch timer = Stopwatch.createStarted();
     int totalBytes = 0;
-    for (T subject : subjects) {
-      byte[] serialized = toBytes(subject);
-      totalBytes += serialized.length;
-      T deserialized = fromBytes(serialized);
-      verificationFunction.verifyDeserialized(subject, deserialized);
+    for (int i = 0; i < repetitions; ++i) {
+      for (T subject : subjects) {
+        byte[] serialized = toBytes(subject);
+        totalBytes += serialized.length;
+        T deserialized = fromBytes(serialized);
+        verificationFunction.verifyDeserialized(subject, deserialized);
+      }
     }
     logger.log(
         Level.INFO,
@@ -143,6 +148,7 @@
     private boolean skipBadDataTest = false;
     private VerificationFunction<T> verificationFunction =
         (original, deserialized) -> assertThat(deserialized).isEqualTo(original);
+    int repetitions = 1;
 
     private Builder(ObjectCodec<T> underTest) {
       this.underTest = underTest;
@@ -185,6 +191,11 @@
       return this;
     }
 
+    public Builder<T> setRepetitions(int repetitions) {
+      this.repetitions = repetitions;
+      return this;
+    }
+
     /** Captures the state of this builder and run all associated tests. */
     public void buildAndRunTests() throws Exception {
       build().runTests();
@@ -202,7 +213,8 @@
           new SerializationContext(dependencies),
           new DeserializationContext(dependencies),
           skipBadDataTest,
-          verificationFunction);
+          verificationFunction,
+          repetitions);
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java
index 5d561ad..d89a428 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java
@@ -66,6 +66,8 @@
   private VerificationFunction verificationFunction =
       (original, deserialized) -> assertThat(deserialized).isEqualTo(original);
 
+  private int repetitions = 1;
+
   public SerializationTester(Object... subjects) {
     Preconditions.checkArgument(subjects.length > 0);
     this.subjects = ImmutableList.copyOf(subjects);
@@ -89,6 +91,12 @@
     return this;
   }
 
+  /** Sets the number of times to repeat serialization and deserialization. */
+  public SerializationTester setRepetitions(int repetitions) {
+    this.repetitions = repetitions;
+    return this;
+  }
+
   public void runTests() throws Exception {
     testSerializeDeserialize();
     testStableSerialization();
@@ -96,14 +104,17 @@
   }
 
   /** Runs serialization/deserialization tests. */
+  @SuppressWarnings("unchecked")
   private void testSerializeDeserialize() throws Exception {
     Stopwatch timer = Stopwatch.createStarted();
     int totalBytes = 0;
-    for (Object subject : subjects) {
-      byte[] serialized = toBytes(subject);
-      totalBytes += serialized.length;
-      Object deserialized = fromBytes(serialized);
-      verificationFunction.verifyDeserialized(subject, deserialized);
+    for (int i = 0; i < repetitions; ++i) {
+      for (Object subject : subjects) {
+        byte[] serialized = toBytes(subject);
+        totalBytes += serialized.length;
+        Object deserialized = fromBytes(serialized);
+        verificationFunction.verifyDeserialized(subject, deserialized);
+      }
     }
     logger.log(
         Level.INFO,