Rename StarlarkImportLookupFunction and friends

This renames "StarlarkImportLookup*" to "BzlLoad*", and also changes a number of uses of "import" to "load". This helps keep names to a more manageable length and is in line with modern Starlark terminology.

This is refactoring work toward adding a new kind of .bzl loading context, for Bazel-internal .bzl files.

Work toward #11437.

RELNOTES: None
PiperOrigin-RevId: 313240742
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
new file mode 100644
index 0000000..c122bdb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
@@ -0,0 +1,871 @@
+// Copyright 2014 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.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.flogger.GoogleLogger;
+import com.google.devtools.build.lib.actions.InconsistentFilesystemException;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.LabelConstants;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.concurrent.BlazeInterners;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.BazelModuleContext;
+import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.packages.StarlarkExportable;
+import com.google.devtools.build.lib.packages.WorkspaceFileValue;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.LoadStatement;
+import com.google.devtools.build.lib.syntax.Location;
+import com.google.devtools.build.lib.syntax.Module;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.StarlarkFile;
+import com.google.devtools.build.lib.syntax.StarlarkSemantics;
+import com.google.devtools.build.lib.syntax.StarlarkThread;
+import com.google.devtools.build.lib.syntax.Statement;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.RecordingSkyFunctionEnvironment;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrException;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * A Skyframe function to look up and load a single .bzl module.
+ *
+ * <p>Given a {@link Label} referencing a .bzl file, attempts to locate the file and load it. The
+ * Label must be absolute, and must not reference the special {@code external} package. If loading
+ * is successful, returns a {@link BzlLoadValue} that encapsulates the loaded {@link Module} and its
+ * transitive digest and {@link StarlarkFileDependency} information. If loading is unsuccessful,
+ * throws a {@link BzlLoadFunctionException} that encapsulates the cause of the failure.
+ */
+public class BzlLoadFunction implements SkyFunction {
+
+  // Creates the BazelStarlarkContext and populates the predeclared .bzl symbols.
+  private final RuleClassProvider ruleClassProvider;
+  // Only used to retrieve the "native" object.
+  private final PackageFactory packageFactory;
+
+  private final ASTFileLookupValueManager astFileLookupValueManager;
+  @Nullable private final SelfInliningManager selfInliningManager;
+
+  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
+
+  private BzlLoadFunction(
+      RuleClassProvider ruleClassProvider,
+      PackageFactory packageFactory,
+      ASTFileLookupValueManager astFileLookupValueManager,
+      @Nullable SelfInliningManager selfInliningManager) {
+    this.ruleClassProvider = ruleClassProvider;
+    this.packageFactory = packageFactory;
+    this.astFileLookupValueManager = astFileLookupValueManager;
+    this.selfInliningManager = selfInliningManager;
+  }
+
+  public static BzlLoadFunction create(
+      RuleClassProvider ruleClassProvider,
+      PackageFactory packageFactory,
+      DigestHashFunction digestHashFunction,
+      Cache<Label, ASTFileLookupValue> astFileLookupValueCache) {
+    return new BzlLoadFunction(
+        ruleClassProvider,
+        packageFactory,
+        // When we are not inlining BzlLoadValue nodes, there is no need to have separate
+        // ASTFileLookupValue nodes for bzl files. Instead we inline them for a strict memory win,
+        // at a small code complexity cost.
+        //
+        // Detailed explanation:
+        // (1) The ASTFileLookupValue node for a bzl file is used only for the computation of
+        // that file's BzlLoadValue node. So there's no concern about duplicate work that would
+        // otherwise get deduped by Skyframe.
+        // (2) ASTFileLookupValue doesn't have an interesting equality relation, so we have no
+        // hope of getting any interesting change-pruning of ASTFileLookupValue nodes. If we
+        // had an interesting equality relation that was e.g. able to ignore benign
+        // whitespace, then there would be a hypothetical benefit to having separate
+        // ASTFileLookupValue nodes (e.g. on incremental builds we'd be able to not re-execute
+        // top-level code in bzl files if the file were reparsed to an equivalent AST).
+        // (3) A ASTFileLookupValue node lets us avoid redoing work on a BzlLoadFunction Skyframe
+        // restart, but we can also achieve that result ourselves with a cache that persists between
+        // Skyframe restarts.
+        //
+        // Therefore, ASTFileLookupValue nodes are wasteful from two perspectives:
+        // (a) ASTFileLookupValue contains a StarlarkFile, and that business object is really
+        // just a temporary thing for bzl execution. Retaining it forever is pure waste.
+        // (b) The memory overhead of the extra Skyframe node and edge per bzl file is pure
+        // waste.
+        new InliningAndCachingASTFileLookupValueManager(
+            ruleClassProvider, digestHashFunction, astFileLookupValueCache),
+        /*selfInliningManager=*/ null);
+  }
+
+  public static BzlLoadFunction createForInliningSelfForPackageAndWorkspaceNodes(
+      RuleClassProvider ruleClassProvider,
+      PackageFactory packageFactory,
+      int bzlLoadValueCacheSize) {
+    return new BzlLoadFunction(
+        ruleClassProvider,
+        packageFactory,
+        // When we are inlining BzlLoadValue nodes, then we want to have explicit ASTFileLookupValue
+        // nodes, since now (1) in the comment above doesn't hold. This way we read and parse each
+        // needed bzl file at most once total globally, rather than once per need (in the worst-case
+        // of a BzlLoadValue inlining cache miss). This is important in the situation where a bzl
+        // file is loaded by a lot of other bzl files or BUILD files.
+        RegularSkyframeASTFileLookupValueManager.INSTANCE,
+        new SelfInliningManager(bzlLoadValueCacheSize));
+  }
+
+  @Override
+  @Nullable
+  public SkyValue compute(SkyKey skyKey, Environment env)
+      throws SkyFunctionException, InterruptedException {
+    BzlLoadValue.Key key = (BzlLoadValue.Key) skyKey.argument();
+    try {
+      return computeInternal(key, env, /*inliningState=*/ null);
+    } catch (InconsistentFilesystemException e) {
+      throw new BzlLoadFunctionException(e, Transience.PERSISTENT);
+    } catch (BzlLoadFailedException e) {
+      throw new BzlLoadFunctionException(e);
+    }
+  }
+
+  @Nullable
+  BzlLoadValue computeWithSelfInlineCallsForPackageAndWorkspaceNodes(
+      BzlLoadValue.Key key,
+      Environment env,
+      Map<BzlLoadValue.Key, CachedBzlLoadValueAndDeps> visitedDepsInToplevelLoad)
+      throws InconsistentFilesystemException, BzlLoadFailedException, InterruptedException {
+    Preconditions.checkNotNull(selfInliningManager);
+    // See comments in computeWithSelfInlineCallsInternal for an explanation of the visitedNested
+    // and visitedDepsInToplevelLoad vars.
+    CachedBzlLoadValueAndDeps cachedBzlLoadValueAndDeps =
+        computeWithSelfInlineCallsInternal(
+            key,
+            env,
+            // visitedNested must use insertion order to display the correct error.
+            /*visitedNested=*/ new LinkedHashSet<>(),
+            /*visitedDepsInToplevelLoad=*/ visitedDepsInToplevelLoad);
+    if (cachedBzlLoadValueAndDeps == null) {
+      return null;
+    }
+    return cachedBzlLoadValueAndDeps.getValue();
+  }
+
+  @Nullable
+  private CachedBzlLoadValueAndDeps computeWithSelfInlineCallsInternal(
+      BzlLoadValue.Key key,
+      Environment env,
+      Set<BzlLoadValue.Key> visitedNested,
+      Map<BzlLoadValue.Key, CachedBzlLoadValueAndDeps> visitedDepsInToplevelLoad)
+      throws InconsistentFilesystemException, BzlLoadFailedException, InterruptedException {
+    // Under BzlLoadFunction inlining, BUILD and WORKSPACE files are evaluated in separate Skyframe
+    // threads, but all the .bzls transitively loaded by a single package occur in one thread. All
+    // these threads share a global cache in selfInliningManager, so that once any thread completes
+    // evaluation of a .bzl, it needn't be evaluated again (unless it's evicted).
+    //
+    // If two threads race to evaluate the same .bzl, each one will see a different copy of it, and
+    // only one will end up in the global cache. This presents a hazard if the same BUILD or
+    // WORKSPACE file has a diamond dependency on foo.bzl, evaluates it the first time, and gets a
+    // different copy of it from the cache the second time. This is because Starlark values may use
+    // object identity, which breaks the moment two distinct observable copies are visible in the
+    // same context (see b/138598337).
+    //
+    // (Note that blocking evaluation of .bzls on retrievals from the global cache doesn't work --
+    // two threads could deadlock while trying to evaluate an illegal load() cycle from opposite
+    // ends.)
+    //
+    // To solve this, we keep a second cache in visitedDepsInToplevelLoad, of just the .bzls
+    // transitively loaded in the current package. The entry for foo.bzl may be a different copy
+    // than the one in the global cache, but the BUILD or WORKSPACE file won't know the difference.
+    // (We don't need to worry about Starlark values from different packages interacting since
+    // inlining is only used for the loading phase.)
+    //
+    CachedBzlLoadValueAndDeps cachedBzlLoadValueAndDeps = visitedDepsInToplevelLoad.get(key);
+    if (cachedBzlLoadValueAndDeps == null) {
+      cachedBzlLoadValueAndDeps = selfInliningManager.bzlLoadValueCache.getIfPresent(key);
+      if (cachedBzlLoadValueAndDeps != null) {
+        cachedBzlLoadValueAndDeps.traverse(env::registerDependencies, visitedDepsInToplevelLoad);
+      }
+    }
+    if (cachedBzlLoadValueAndDeps != null) {
+      return cachedBzlLoadValueAndDeps;
+    }
+
+    // visitedNested is keyed on the SkyKey, not the label, because it's possible for distinct keys
+    // to share the same label. Examples include the "@builtins" pseudo-repo vs a real repository
+    // that happens to be named "@builtins", or keys for the same .bzl with different workspace
+    // chunking information. It's unclear whether these particular cycles can arise in practice, but
+    // it doesn't hurt to be robust to future changes that may make that possible.
+    if (!visitedNested.add(key)) {
+      ImmutableList<BzlLoadValue.Key> cycle =
+          CycleUtils.splitIntoPathAndChain(Predicates.equalTo(key), visitedNested).second;
+      throw new BzlLoadFailedException("Starlark load cycle: " + cycle);
+    }
+
+    CachedBzlLoadValueAndDeps.Builder inlineCachedValueBuilder =
+        selfInliningManager.cachedBzlLoadValueAndDepsBuilderFactory
+            .newCachedBzlLoadValueAndDepsBuilder();
+    // Use an instrumented Skyframe env to capture Skyframe deps in the CachedBzlLoadValueAndDeps.
+    // This is transitive but doesn't include deps underneath recursively loaded .bzls (the
+    // recursion uses the unwrapped original env).
+    Preconditions.checkState(
+        !(env instanceof RecordingSkyFunctionEnvironment),
+        "Found nested RecordingSkyFunctionEnvironment but it should have been stripped: %s",
+        env);
+    RecordingSkyFunctionEnvironment recordingEnv =
+        new RecordingSkyFunctionEnvironment(
+            env,
+            inlineCachedValueBuilder::addDep,
+            inlineCachedValueBuilder::addDeps,
+            inlineCachedValueBuilder::noteException);
+    BzlLoadValue value =
+        computeInternal(
+            key,
+            recordingEnv,
+            new InliningState(visitedNested, inlineCachedValueBuilder, visitedDepsInToplevelLoad));
+    // All loads traversed, this key can no longer be part of a cycle.
+    Preconditions.checkState(visitedNested.remove(key), key);
+
+    if (value != null) {
+      inlineCachedValueBuilder.setValue(value);
+      inlineCachedValueBuilder.setKey(key);
+      cachedBzlLoadValueAndDeps = inlineCachedValueBuilder.build();
+      visitedDepsInToplevelLoad.put(key, cachedBzlLoadValueAndDeps);
+      selfInliningManager.bzlLoadValueCache.put(key, cachedBzlLoadValueAndDeps);
+    }
+    return cachedBzlLoadValueAndDeps;
+  }
+
+  public void resetSelfInliningCache() {
+    selfInliningManager.reset();
+  }
+
+  private static ContainingPackageLookupValue getContainingPackageLookupValue(
+      Environment env, Label label)
+      throws InconsistentFilesystemException, BzlLoadFailedException, InterruptedException {
+    PathFragment dir = Label.getContainingDirectory(label);
+    PackageIdentifier dirId =
+        PackageIdentifier.create(label.getPackageIdentifier().getRepository(), dir);
+    ContainingPackageLookupValue containingPackageLookupValue;
+    try {
+      containingPackageLookupValue =
+          (ContainingPackageLookupValue)
+              env.getValueOrThrow(
+                  ContainingPackageLookupValue.key(dirId),
+                  BuildFileNotFoundException.class,
+                  InconsistentFilesystemException.class);
+    } catch (BuildFileNotFoundException e) {
+      throw BzlLoadFailedException.errorReadingFile(
+          label.toPathFragment(), new ErrorReadingStarlarkExtensionException(e));
+    }
+    if (containingPackageLookupValue == null) {
+      return null;
+    }
+    // Ensure the label doesn't cross package boundaries.
+    if (!containingPackageLookupValue.hasContainingPackage()) {
+      throw BzlLoadFailedException.noBuildFile(
+          label, containingPackageLookupValue.getReasonForNoContainingPackage());
+    }
+    if (!containingPackageLookupValue
+        .getContainingPackageName()
+        .equals(label.getPackageIdentifier())) {
+      throw BzlLoadFailedException.labelCrossesPackageBoundary(label, containingPackageLookupValue);
+    }
+    return containingPackageLookupValue;
+  }
+
+  private static class InliningState {
+    private final Set<BzlLoadValue.Key> visitedNested;
+    private final CachedBzlLoadValueAndDeps.Builder inlineCachedValueBuilder;
+    private final Map<BzlLoadValue.Key, CachedBzlLoadValueAndDeps> visitedDepsInToplevelLoad;
+
+    private InliningState(
+        Set<BzlLoadValue.Key> visitedNested,
+        CachedBzlLoadValueAndDeps.Builder inlineCachedValueBuilder,
+        Map<BzlLoadValue.Key, CachedBzlLoadValueAndDeps> visitedDepsInToplevelLoad) {
+      this.visitedNested = visitedNested;
+      this.inlineCachedValueBuilder = inlineCachedValueBuilder;
+      this.visitedDepsInToplevelLoad = visitedDepsInToplevelLoad;
+    }
+  }
+
+  // It is vital that we don't return any value if any call to env#getValue(s)OrThrow throws an
+  // exception. We are allowed to wrap the thrown exception and rethrow it for any calling functions
+  // to handle though.
+  @Nullable
+  private BzlLoadValue computeInternal(
+      BzlLoadValue.Key key, Environment env, @Nullable InliningState inliningState)
+      throws InconsistentFilesystemException, BzlLoadFailedException, InterruptedException {
+    Label label = key.getLabel();
+    PathFragment filePath = label.toPathFragment();
+
+    StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
+    if (starlarkSemantics == null) {
+      return null;
+    }
+
+    if (getContainingPackageLookupValue(env, label) == null) {
+      return null;
+    }
+
+    // Load the AST corresponding to this file.
+    ASTFileLookupValue astLookupValue;
+    try {
+      astLookupValue = astFileLookupValueManager.getASTFileLookupValue(label, env);
+    } catch (ErrorReadingStarlarkExtensionException e) {
+      throw BzlLoadFailedException.errorReadingFile(filePath, e);
+    }
+    if (astLookupValue == null) {
+      return null;
+    }
+
+    BzlLoadValue result = null;
+    try {
+      result =
+          computeInternalWithAst(
+              key, filePath, starlarkSemantics, astLookupValue, env, inliningState);
+    } catch (InconsistentFilesystemException | BzlLoadFailedException | InterruptedException e) {
+      astFileLookupValueManager.doneWithASTFileLookupValue(label);
+      throw e;
+    }
+    if (result != null) {
+      // Result is final (no Skyframe restart), so no further need for the AST value.
+      astFileLookupValueManager.doneWithASTFileLookupValue(label);
+    }
+    return result;
+  }
+
+  @Nullable
+  private BzlLoadValue computeInternalWithAst(
+      BzlLoadValue.Key key,
+      PathFragment filePath,
+      StarlarkSemantics starlarkSemantics,
+      ASTFileLookupValue astLookupValue,
+      Environment env,
+      @Nullable InliningState inliningState)
+      throws InconsistentFilesystemException, BzlLoadFailedException, InterruptedException {
+    Label label = key.getLabel();
+
+    if (!astLookupValue.lookupSuccessful()) {
+      // Starlark code must exist.
+      throw new BzlLoadFailedException(astLookupValue.getError());
+    }
+    StarlarkFile file = astLookupValue.getAST();
+    if (!file.ok()) {
+      throw BzlLoadFailedException.skylarkErrors(filePath);
+    }
+
+    // Process the load statements in the file,
+    // resolving labels relative to the current repo mapping.
+    ImmutableMap<RepositoryName, RepositoryName> repoMapping = getRepositoryMapping(key, env);
+    if (repoMapping == null) {
+      return null;
+    }
+    List<Pair<String, Label>> loads =
+        getLoadLabels(env.getListener(), file, label.getPackageIdentifier(), repoMapping);
+    if (loads == null) {
+      // malformed load statements
+      throw BzlLoadFailedException.skylarkErrors(filePath);
+    }
+
+    // Compute Skyframe key for each label in 'loads'.
+    List<BzlLoadValue.Key> loadKeys = Lists.newArrayListWithExpectedSize(loads.size());
+    for (Pair<String, Label> load : loads) {
+      loadKeys.add(key.getKeyForLoad(load.second));
+    }
+
+    // Load .bzl modules in parallel.
+    List<BzlLoadValue> bzlLoads =
+        inliningState == null
+            ? computeBzlLoadsNoInlining(env, loadKeys, file.getStartLocation())
+            : computeBzlLoadsWithSelfInlining(env, loadKeys, label, inliningState);
+    if (bzlLoads == null) {
+      return null; // Skyframe deps unavailable
+    }
+
+    // Process the loaded modules.
+    //
+    // Compute a digest of the file itself plus the transitive hashes of the modules it directly
+    // loads. Loop iteration order matches the source order of load statements.
+    Fingerprint fp = new Fingerprint();
+    fp.addBytes(astLookupValue.getDigest());
+    Map<String, Module> loadedModules = Maps.newHashMapWithExpectedSize(loads.size());
+    ImmutableList.Builder<StarlarkFileDependency> fileDependencies =
+        ImmutableList.builderWithExpectedSize(loads.size());
+    for (int i = 0; i < loads.size(); i++) {
+      String loadString = loads.get(i).first;
+      BzlLoadValue v = bzlLoads.get(i);
+      loadedModules.put(loadString, v.getModule());
+      fileDependencies.add(v.getDependency());
+      fp.addBytes(v.getTransitiveDigest());
+    }
+    byte[] transitiveDigest = fp.digestAndReset();
+
+    // executeModule does not request values from the Environment. It may post events to the
+    // Environment, but events do not matter when caching BzlLoadValues.
+    Module module =
+        executeModule(
+            file,
+            key.getLabel(),
+            transitiveDigest,
+            loadedModules,
+            starlarkSemantics,
+            env,
+            /*inWorkspace=*/ key instanceof BzlLoadValue.WorkspaceBzlKey,
+            repoMapping);
+    BzlLoadValue result =
+        new BzlLoadValue(
+            module, transitiveDigest, new StarlarkFileDependency(label, fileDependencies.build()));
+    return result;
+  }
+
+  private static ImmutableMap<RepositoryName, RepositoryName> getRepositoryMapping(
+      BzlLoadValue.Key key, Environment env) throws InterruptedException {
+    Label enclosingFileLabel = key.getLabel();
+
+    ImmutableMap<RepositoryName, RepositoryName> repositoryMapping;
+    if (key instanceof BzlLoadValue.WorkspaceBzlKey) {
+      // Still during workspace file evaluation
+      BzlLoadValue.WorkspaceBzlKey workspaceBzlKey = (BzlLoadValue.WorkspaceBzlKey) key;
+      if (workspaceBzlKey.getWorkspaceChunk() == 0) {
+        // There is no previous workspace chunk
+        repositoryMapping = ImmutableMap.of();
+      } else {
+        SkyKey workspaceFileKey =
+            WorkspaceFileValue.key(
+                workspaceBzlKey.getWorkspacePath(), workspaceBzlKey.getWorkspaceChunk() - 1);
+        WorkspaceFileValue workspaceFileValue = (WorkspaceFileValue) env.getValue(workspaceFileKey);
+        // Note: we know for sure that the requested WorkspaceFileValue is fully computed so we do
+        // not need to check if it is null
+        repositoryMapping =
+            workspaceFileValue
+                .getRepositoryMapping()
+                .getOrDefault(
+                    enclosingFileLabel.getPackageIdentifier().getRepository(), ImmutableMap.of());
+      }
+    } else {
+      // We are fully done with workspace evaluation so we should get the mappings from the
+      // final RepositoryMappingValue
+      PackageIdentifier packageIdentifier = enclosingFileLabel.getPackageIdentifier();
+      RepositoryMappingValue repositoryMappingValue =
+          (RepositoryMappingValue)
+              env.getValue(RepositoryMappingValue.key(packageIdentifier.getRepository()));
+      if (repositoryMappingValue == null) {
+        return null;
+      }
+      repositoryMapping = repositoryMappingValue.getRepositoryMapping();
+    }
+    return repositoryMapping;
+  }
+
+  /**
+   * Returns a list of pairs mapping each load string in the BUILD or .bzl file to the Label it
+   * resolves to. Labels are resolved relative to {@code base}, the file's package. If any load
+   * statement is malformed, the function reports one or more errors to the handler and returns
+   * null. Order matches the source.
+   */
+  @Nullable
+  static List<Pair<String, Label>> getLoadLabels(
+      EventHandler handler,
+      StarlarkFile file,
+      PackageIdentifier base,
+      ImmutableMap<RepositoryName, RepositoryName> repoMapping) {
+    Preconditions.checkArgument(!base.getRepository().isDefault());
+
+    // It's redundant that getRelativeWithRemapping needs a Label;
+    // a PackageIdentifier should suffice. Make one here.
+    Label buildLabel = getBUILDLabel(base);
+
+    boolean ok = true;
+    List<Pair<String, Label>> loads = Lists.newArrayList();
+    for (Statement stmt : file.getStatements()) {
+      if (stmt instanceof LoadStatement) {
+        LoadStatement load = (LoadStatement) stmt;
+        String module = load.getImport().getValue();
+
+        // Parse the load statement's module string as a label.
+        // It must end in .bzl and not be in package "//external".
+        try {
+          Label label = buildLabel.getRelativeWithRemapping(module, repoMapping);
+          if (!label.getName().endsWith(".bzl")) {
+            throw new LabelSyntaxException("The label must reference a file with extension '.bzl'");
+          }
+          if (label.getPackageIdentifier().equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER)) {
+            throw new LabelSyntaxException(
+                "Starlark files may not be loaded from the //external package");
+          }
+          loads.add(Pair.of(module, label));
+        } catch (LabelSyntaxException ex) {
+          handler.handle(
+              Event.error(
+                  load.getImport().getStartLocation(), "in load statement: " + ex.getMessage()));
+          ok = false;
+        }
+      }
+    }
+    return ok ? loads : null;
+  }
+
+  private static Label getBUILDLabel(PackageIdentifier pkgid) {
+    try {
+      return Label.create(pkgid, "BUILD");
+    } catch (LabelSyntaxException e) {
+      // Shouldn't happen; the Label is well-formed by construction.
+      throw new IllegalStateException(e);
+    }
+  }
+
+  /**
+   * Compute the BzlLoadValue for all given keys using vanilla Skyframe evaluation, returning {@code
+   * null} if Skyframe deps were missing and have been requested.
+   */
+  @Nullable
+  private static List<BzlLoadValue> computeBzlLoadsNoInlining(
+      Environment env, List<BzlLoadValue.Key> keys, Location locationForErrors)
+      throws BzlLoadFailedException, InterruptedException {
+    List<BzlLoadValue> bzlLoads = Lists.newArrayListWithExpectedSize(keys.size());
+    Map<SkyKey, ValueOrException<BzlLoadFailedException>> values =
+        env.getValuesOrThrow(keys, BzlLoadFailedException.class);
+    // Uses same order as load()s in the file. Order matters since we report the first error.
+    for (BzlLoadValue.Key key : keys) {
+      try {
+        bzlLoads.add((BzlLoadValue) values.get(key).get());
+      } catch (BzlLoadFailedException exn) {
+        throw new BzlLoadFailedException(
+            "in " + locationForErrors.file() + ": " + exn.getMessage());
+      }
+    }
+    return env.valuesMissing() ? null : bzlLoads;
+  }
+
+  /**
+   * Compute the BzlLoadValue for all given keys by reusing this instance of the BzlLoadFunction,
+   * bypassing traditional Skyframe evaluation, returning {@code null} if Skyframe deps were missing
+   * and have been requested.
+   */
+  @Nullable
+  private List<BzlLoadValue> computeBzlLoadsWithSelfInlining(
+      Environment env, List<BzlLoadValue.Key> keys, Label fileLabel, InliningState inliningState)
+      throws InterruptedException, BzlLoadFailedException, InconsistentFilesystemException {
+    Preconditions.checkState(
+        env instanceof RecordingSkyFunctionEnvironment,
+        "Expected to be recording dep requests when inlining BzlLoadFunction: %s",
+        fileLabel);
+    Environment strippedEnv = ((RecordingSkyFunctionEnvironment) env).getDelegate();
+    List<BzlLoadValue> bzlLoads = Lists.newArrayListWithExpectedSize(keys.size());
+    Exception deferredException = null;
+    boolean valuesMissing = false;
+    // NOTE: Iterating over loads in the order listed in the file.
+    for (BzlLoadValue.Key key : keys) {
+      CachedBzlLoadValueAndDeps cachedValue;
+      try {
+        cachedValue =
+            computeWithSelfInlineCallsInternal(
+                key,
+                strippedEnv,
+                inliningState.visitedNested,
+                inliningState.visitedDepsInToplevelLoad);
+      } catch (BzlLoadFailedException | InconsistentFilesystemException e) {
+        // For determinism's sake while inlining, preserve the first exception and continue to run
+        // subsequently listed loads to completion/exception, loading all transitive deps anyway.
+        deferredException = MoreObjects.firstNonNull(deferredException, e);
+        continue;
+      }
+      if (cachedValue == null) {
+        Preconditions.checkState(env.valuesMissing(), "no starlark load value for %s", key);
+        // We continue making inline calls even if some requested values are missing, to maximize
+        // the number of dependent (non-inlined) SkyFunctions that are requested, thus avoiding a
+        // quadratic number of restarts.
+        valuesMissing = true;
+      } else {
+        bzlLoads.add(cachedValue.getValue());
+        inliningState.inlineCachedValueBuilder.addTransitiveDeps(cachedValue);
+      }
+    }
+    if (deferredException != null) {
+      Throwables.throwIfInstanceOf(deferredException, BzlLoadFailedException.class);
+      Throwables.throwIfInstanceOf(deferredException, InconsistentFilesystemException.class);
+      throw new IllegalStateException(
+          "caught a checked exception of unexpected type", deferredException);
+    }
+    return valuesMissing ? null : bzlLoads;
+  }
+
+  /** Executes the .bzl file defining the module to be loaded. */
+  private Module executeModule(
+      StarlarkFile file,
+      Label label,
+      byte[] transitiveDigest,
+      Map<String, Module> loadedModules,
+      StarlarkSemantics starlarkSemantics,
+      Environment env,
+      boolean inWorkspace,
+      ImmutableMap<RepositoryName, RepositoryName> repositoryMapping)
+      throws BzlLoadFailedException, InterruptedException {
+    // set up .bzl predeclared environment
+    Map<String, Object> predeclared = new HashMap<>(ruleClassProvider.getEnvironment());
+    predeclared.put("native", packageFactory.getNativeModule(inWorkspace));
+    Module module = Module.withPredeclared(starlarkSemantics, predeclared);
+    module.setClientData(BazelModuleContext.create(label, transitiveDigest));
+
+    try (Mutability mu = Mutability.create("loading", label)) {
+      StarlarkThread thread = new StarlarkThread(mu, starlarkSemantics);
+      thread.setLoader(loadedModules::get);
+      StoredEventHandler eventHandler = new StoredEventHandler();
+      thread.setPrintHandler(Event.makeDebugPrintHandler(eventHandler));
+      ruleClassProvider.setStarlarkThreadContext(thread, label, repositoryMapping);
+      execAndExport(file, label, eventHandler, module, thread);
+
+      Event.replayEventsOn(env.getListener(), eventHandler.getEvents());
+      for (Postable post : eventHandler.getPosts()) {
+        env.getListener().post(post);
+      }
+      if (eventHandler.hasErrors()) {
+        throw BzlLoadFailedException.errors(label.toPathFragment());
+      }
+      return module;
+    }
+  }
+
+  // Precondition: file is validated and error-free.
+  // Precondition: thread has a valid transitiveDigest.
+  // TODO(adonovan): executeModule would make a better public API than this function.
+  public static void execAndExport(
+      StarlarkFile file, Label label, EventHandler handler, Module module, StarlarkThread thread)
+      throws InterruptedException {
+
+    // Intercept execution after every assignment at top level
+    // and "export" any newly assigned exportable globals.
+    // TODO(adonovan): change the semantics; see b/65374671.
+    thread.setPostAssignHook(
+        (name, value) -> {
+          if (value instanceof StarlarkExportable) {
+            StarlarkExportable exp = (StarlarkExportable) value;
+            if (!exp.isExported()) {
+              try {
+                exp.export(label, name);
+              } catch (EvalException ex) {
+                handler.handle(Event.error(ex.getLocation(), ex.getMessage()));
+              }
+            }
+          }
+        });
+
+    try {
+      EvalUtils.exec(file, module, thread);
+    } catch (EvalException ex) {
+      handler.handle(Event.error(ex.getLocation(), ex.getMessage()));
+    }
+  }
+
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  static final class BzlLoadFailedException extends Exception implements SaneAnalysisException {
+    private final Transience transience;
+
+    private BzlLoadFailedException(String errorMessage) {
+      super(errorMessage);
+      this.transience = Transience.PERSISTENT;
+    }
+
+    private BzlLoadFailedException(String errorMessage, Exception cause, Transience transience) {
+      super(errorMessage, cause);
+      this.transience = transience;
+    }
+
+    static BzlLoadFailedException errors(PathFragment file) {
+      return new BzlLoadFailedException(String.format("Extension file '%s' has errors", file));
+    }
+
+    static BzlLoadFailedException errorReadingFile(
+        PathFragment file, ErrorReadingStarlarkExtensionException cause) {
+      return new BzlLoadFailedException(
+          String.format(
+              "Encountered error while reading extension file '%s': %s", file, cause.getMessage()),
+          cause,
+          cause.getTransience());
+    }
+
+    static BzlLoadFailedException noBuildFile(Label file, @Nullable String reason) {
+      if (reason != null) {
+        return new BzlLoadFailedException(
+            String.format("Unable to find package for %s: %s.", file, reason));
+      }
+      return new BzlLoadFailedException(
+          String.format(
+              "Every .bzl file must have a corresponding package, but '%s' does not have one."
+                  + " Please create a BUILD file in the same or any parent directory. Note that"
+                  + " this BUILD file does not need to do anything except exist.",
+              file));
+    }
+
+    static BzlLoadFailedException labelCrossesPackageBoundary(
+        Label label, ContainingPackageLookupValue containingPackageLookupValue) {
+      return new BzlLoadFailedException(
+          ContainingPackageLookupValue.getErrorMessageForLabelCrossingPackageBoundary(
+              // We don't actually know the proper Root to pass in here (since we don't e.g. know
+              // the root of the bzl/BUILD file that is trying to load 'label'). Therefore we just
+              // pass in the Root of the containing package in order to still get a useful error
+              // message for the user.
+              containingPackageLookupValue.getContainingPackageRoot(),
+              label,
+              containingPackageLookupValue));
+    }
+
+    static BzlLoadFailedException skylarkErrors(PathFragment file) {
+      return new BzlLoadFailedException(String.format("Extension '%s' has errors", file));
+    }
+  }
+
+  private interface ASTFileLookupValueManager {
+    @Nullable
+    ASTFileLookupValue getASTFileLookupValue(Label label, Environment env)
+        throws InconsistentFilesystemException, InterruptedException,
+            ErrorReadingStarlarkExtensionException;
+
+    void doneWithASTFileLookupValue(Label label);
+  }
+
+  private static class RegularSkyframeASTFileLookupValueManager
+      implements ASTFileLookupValueManager {
+    private static final RegularSkyframeASTFileLookupValueManager INSTANCE =
+        new RegularSkyframeASTFileLookupValueManager();
+
+    @Nullable
+    @Override
+    public ASTFileLookupValue getASTFileLookupValue(Label label, Environment env)
+        throws InconsistentFilesystemException, InterruptedException,
+            ErrorReadingStarlarkExtensionException {
+      return (ASTFileLookupValue)
+          env.getValueOrThrow(
+              ASTFileLookupValue.key(label),
+              ErrorReadingStarlarkExtensionException.class,
+              InconsistentFilesystemException.class);
+    }
+
+    @Override
+    public void doneWithASTFileLookupValue(Label label) {}
+  }
+
+  private static class InliningAndCachingASTFileLookupValueManager
+      implements ASTFileLookupValueManager {
+    private final RuleClassProvider ruleClassProvider;
+    private final DigestHashFunction digestHashFunction;
+    // We keep a cache of ASTFileLookupValues that have been computed but whose corresponding
+    // BzlLoadValue has not yet completed. This avoids repeating the ASTFileLookupValue work in case
+    // of Skyframe restarts. (If we weren't inlining, Skyframe would cache this for us.)
+    private final Cache<Label, ASTFileLookupValue> astFileLookupValueCache;
+
+    private InliningAndCachingASTFileLookupValueManager(
+        RuleClassProvider ruleClassProvider,
+        DigestHashFunction digestHashFunction,
+        Cache<Label, ASTFileLookupValue> astFileLookupValueCache) {
+      this.ruleClassProvider = ruleClassProvider;
+      this.digestHashFunction = digestHashFunction;
+      this.astFileLookupValueCache = astFileLookupValueCache;
+    }
+
+    @Nullable
+    @Override
+    public ASTFileLookupValue getASTFileLookupValue(Label label, Environment env)
+        throws InconsistentFilesystemException, InterruptedException,
+            ErrorReadingStarlarkExtensionException {
+      ASTFileLookupValue value = astFileLookupValueCache.getIfPresent(label);
+      if (value == null) {
+        value =
+            ASTFileLookupFunction.computeInline(
+                ASTFileLookupValue.key(label), env, ruleClassProvider, digestHashFunction);
+        if (value != null) {
+          astFileLookupValueCache.put(label, value);
+        }
+      }
+      return value;
+    }
+
+    @Override
+    public void doneWithASTFileLookupValue(Label label) {
+      astFileLookupValueCache.invalidate(label);
+    }
+  }
+
+  private static class SelfInliningManager {
+    private final int bzlLoadValueCacheSize;
+    private Cache<BzlLoadValue.Key, CachedBzlLoadValueAndDeps> bzlLoadValueCache;
+    private CachedBzlLoadValueAndDepsBuilderFactory cachedBzlLoadValueAndDepsBuilderFactory =
+        new CachedBzlLoadValueAndDepsBuilderFactory();
+
+    private SelfInliningManager(int bzlLoadValueCacheSize) {
+      this.bzlLoadValueCacheSize = bzlLoadValueCacheSize;
+    }
+
+    private void reset() {
+      if (bzlLoadValueCache != null) {
+        logger.atInfo().log(
+            "Starlark inlining cache stats from earlier build: " + bzlLoadValueCache.stats());
+      }
+      cachedBzlLoadValueAndDepsBuilderFactory = new CachedBzlLoadValueAndDepsBuilderFactory();
+      Preconditions.checkState(
+          bzlLoadValueCacheSize >= 0,
+          "Expected positive Starlark cache size if caching. %s",
+          bzlLoadValueCacheSize);
+      bzlLoadValueCache =
+          CacheBuilder.newBuilder()
+              .concurrencyLevel(BlazeInterners.concurrencyLevel())
+              .maximumSize(bzlLoadValueCacheSize)
+              .recordStats()
+              .build();
+    }
+  }
+
+  private static final class BzlLoadFunctionException extends SkyFunctionException {
+    private BzlLoadFunctionException(BzlLoadFailedException cause) {
+      super(cause, cause.transience);
+    }
+
+    private BzlLoadFunctionException(InconsistentFilesystemException e, Transience transience) {
+      super(e, transience);
+    }
+  }
+}