Skip to content

Commit

Permalink
Introduce FileOpNodeMemoizingLookup for tracking file dependencies.
Browse files Browse the repository at this point in the history
This change introduces `FileOpNodeMemoizingLookup`, a new class
responsible for accurately determining the file dependencies of both
analysis (configured target) and execution (action) SkyValues.

**Key Changes:**

*   **FileOpNodeMemoizingLookup:** This new class efficiently computes and
    caches file dependencies by performing a concurrent, memoized, and recursive
    traversal of Skyframe edges. It distinguishes between analysis
    and execution file dependencies, storing them separately within
    `FileOpNode` (previously `FileSystemOperationNode`).

*   **Refined `AbstractNestedFileOpNodes`:** The `AbstractNestedFileOpNodes`
   class (which is used to store a set of `FileOpNode`s) is now split into two
   subclasses:
    *   `NestedFileOpNodes`: Represents a set of analysis file dependencies
         *without* any source file dependencies.
    *   `NestedFileOpNodesWithSources`: Represents a set of analysis file
         dependencies that *includes* immediate source file dependencies.

    This separation allows a more precise representation of dependencies,
    distinguishing between files depended on during analysis and those
    depended on during execution. Source dependencies, which correspond
    to `InputFileConfiguredTarget` instances, are declared during
    analysis but are only truly depended upon by the actions owned by
    the corresponding configured targets, not the configured targets
    themselves. This distinction optimizes storage by allowing both
    configured targets and actions to share the same underlying file
    dependency graph structure.

*   **Consolidated `FileOpNodeOrFuture`:** The `FileSystemOperationNode`
    class (and related classes) have been renamed to `FileOpNode` and moved
    into the newly added `FileOpNodeOrFuture` interface. This consolidation
    improves code readability by centralizing the hierarchical structure of
    file operation nodes.

*   **Renamed `FileOpNode`:** The old name, `FileSystemOperationNode`,
    was becoming cumbersome. The new name, `FileOpNode`, is more concise.

PiperOrigin-RevId: 717556933
Change-Id: I7376f1d5ffb30e520af0e15140d00159f2a78d6d
  • Loading branch information
aoeui authored and copybara-github committed Jan 20, 2025
1 parent 04a44fd commit b704fdb
Show file tree
Hide file tree
Showing 15 changed files with 830 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2025 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.EmptyFileOpNode.EMPTY_FILE_OP_NODE;

import com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.FileOpNode;
import com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.FileOpNodeOrEmpty;
import java.util.Collection;
import javax.annotation.Nullable;

/**
* Represents a collection of {@link FileOpNode}s, allowing for nested structures to represent
* complex file dependencies.
*
* <p>This class serves as a container for multiple {@link FileOpNode} instances, enabling the
* representation of file operation dependencies in a hierarchical manner. It differentiates between
* analysis dependencies (for example, BUILD and .bzl files) and "source" dependencies, used during
* execution (for example, .cpp, .h or .java files). It keeps them together to optimize storage.
*
* <p><b>Source vs. Analysis Dependencies:</b>
*
* <ul>
* <li><b>Analysis:</b> During the analysis phase, source files are declared, but configured
* targets (which define actions) do not depend on the <i>contents</i> of these source files,
* for example, .cpp, .h or .java files.
* <li><b>Execution:</b> The execution phase creates actual dependencies on the contents of source
* files as actions are run.
* </ul>
*
* <p><b>Why combine them?</b> <br>
* Logically, source and analysis dependencies could be tracked separately with different {@link
* FileOpNode}s. However, this would duplicate the dependency graph structure in persistent storage,
* which is expensive. This class keeps them together, trading off a bit of complexity for reduced
* storage overhead. The structure is written only once, and the interpretation of dependencies must
* be handled by the client.
*
* <p><b>Subclasses:</b>
*
* <ul>
* <li>{@link NestedFileOpNodes}: Represents a set of {@link FileOpNode}s without any immediate
* source file dependencies.
* <li>{@link NestedFileOpNodesWithSources}: Represents a set of {@link FileOpNode}s along with a
* list of immediate source file dependencies ({@link FileKey}s).
* </ul>
*/
public abstract sealed class AbstractNestedFileOpNodes implements FileOpNodeOrFuture.FileOpNode
permits AbstractNestedFileOpNodes.NestedFileOpNodes,
AbstractNestedFileOpNodes.NestedFileOpNodesWithSources {
private final FileOpNode[] analysisDependencies;

/**
* Opaque storage for use by serialization.
*
* <p>{@link FileOpNode}, {@link FileKey} and {@link DirectoryListingKey} are mutually dependent
* via {@link FileOpNode}. This type is opaque to avoid forcing {@link FileKey} and {@link
* DirectoryListingKey} to depend on serialization implementation code.
*
* <p>The serialization implementation initializes this field with double-checked locking so it is
* marked volatile.
*/
private volatile Object serializationScratch;

/**
* Effectively, a factory method for {@link NestedFileOpNodes}, but formally a factory method for
* {@link FileOpNodeOrEmpty}.
*
* <p>Returns {@link EMPTY_FILE_OP_NODE} if {@code analysisDependencies} is empty. When {@code
* analysisDependencies} contains only one node, returns the node directly instead of wrapping it.
* Otherwise, returns a {@link NestedFileOpNodes} instance wrapping {@code analysisDependencies}.
*/
public static FileOpNodeOrEmpty from(Collection<FileOpNode> analysisDependencies) {
if (analysisDependencies.isEmpty()) {
return EMPTY_FILE_OP_NODE;
}
if (analysisDependencies.size() == 1) {
return analysisDependencies.iterator().next();
}
return new NestedFileOpNodes(analysisDependencies.toArray(FileOpNode[]::new));
}

/**
* Creates {@link NestedFileOpNodesWithSources} with reductions similar to {@link
* #from(Collection<FileOpNode>)}.
*/
public static FileOpNodeOrEmpty from(
Collection<FileOpNode> analysisDependencies, Collection<FileKey> sources) {
if (sources.isEmpty()) {
return from(analysisDependencies);
}
// It's unclear if `analysisDependencies` can ever be empty here in practice, but it's
// permitted. It should be rare enough that defining a special type for it isn't worth it.
return new NestedFileOpNodesWithSources(
analysisDependencies.toArray(FileOpNode[]::new), sources.toArray(FileKey[]::new));
}

private AbstractNestedFileOpNodes(FileOpNode[] analysisDependencies) {
this.analysisDependencies = analysisDependencies;
}

public int analysisDependenciesCount() {
return analysisDependencies.length;
}

public FileOpNode getAnalysisDependency(int index) {
return analysisDependencies[index];
}

@Nullable
public Object getSerializationScratch() {
return serializationScratch;
}

public void setSerializationScratch(Object value) {
this.serializationScratch = value;
}

/** A set of {@link FileOpNode}s with no immediate source dependencies. */
public static final class NestedFileOpNodes extends AbstractNestedFileOpNodes {
private NestedFileOpNodes(FileOpNode[] analysisDependencies) {
super(analysisDependencies);
checkArgument(analysisDependencies.length > 0);
}
}

/** A set of analysis and source file dependencies. */
public static final class NestedFileOpNodesWithSources extends AbstractNestedFileOpNodes {
private final FileKey[] sources; // never empty

private NestedFileOpNodesWithSources(FileOpNode[] nodes, FileKey[] sources) {
super(nodes);
this.sources = sources;
}

public int sourceCount() {
return sources.length;
}

public FileKey getSource(int i) {
return sources[i];
}
}
}
5 changes: 3 additions & 2 deletions src/main/java/com/google/devtools/build/lib/skyframe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1547,13 +1547,14 @@ java_library(
java_library(
name = "filesystem_keys",
srcs = [
"AbstractNestedFileOpNodes.java",
"DirectoryListingKey.java",
"FileKey.java",
"FileSystemOperationNode.java",
"NestedFileSystemOperationNodes.java",
"FileOpNodeOrFuture.java",
],
deps = [
":sky_functions",
"//src/main/java/com/google/devtools/build/lib/concurrent:settable_future_keyed_value",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization:visible-for-serialization",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
"//src/main/java/com/google/devtools/build/lib/vfs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/** Key for {@link DirectoryListingFunction}. */
@AutoCodec
public final class DirectoryListingKey extends AbstractSkyKey<RootedPath>
implements FileSystemOperationNode {
implements FileOpNodeOrFuture.FileOpNode {

private static final SkyKeyInterner<DirectoryListingKey> interner = SkyKey.newInterner();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@

/** Key for {@link FileFunction}. */
@AutoCodec
public final class FileKey extends AbstractSkyKey<RootedPath> implements FileSystemOperationNode {
public final class FileKey extends AbstractSkyKey<RootedPath>
implements FileOpNodeOrFuture.FileOpNode {
private static final SkyKeyInterner<FileKey> interner = SkyKey.newInterner();

public static FileKey create(RootedPath arg) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 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.devtools.build.lib.concurrent.SettableFutureKeyedValue;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.function.BiConsumer;

/**
* A possibly empty nested set of file system operations.
*
* <p>This value represents the set of file system operation dependencies of a given Skyframe entry,
* computed by Skyframe graph traversal.
*/
@SuppressWarnings("InterfaceWithOnlyStatics") // sealed hierarchy root
public sealed interface FileOpNodeOrFuture
permits FileOpNodeOrFuture.FileOpNodeOrEmpty, FileOpNodeOrFuture.FutureFileOpNode {

/** A possibly empty set of file system dependencies. */
sealed interface FileOpNodeOrEmpty extends FileOpNodeOrFuture
permits EmptyFileOpNode, FileOpNode {}

/** A non-empty set of filesystem operations. */
sealed interface FileOpNode extends FileOpNodeOrEmpty
permits FileKey, DirectoryListingKey, AbstractNestedFileOpNodes {}

/** Empty set of filesystem dependencies. */
enum EmptyFileOpNode implements FileOpNodeOrEmpty {
EMPTY_FILE_OP_NODE;
}

/** The in-flight computation of a {@link FileOpNodeOrEmpty}. */
static final class FutureFileOpNode
extends SettableFutureKeyedValue<FutureFileOpNode, SkyKey, FileOpNodeOrEmpty>
implements FileOpNodeOrFuture {
public FutureFileOpNode(SkyKey key, BiConsumer<SkyKey, FileOpNodeOrEmpty> consumer) {
super(key, consumer);
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ java_library(
],
)

java_library(
name = "file_op_node_map",
srcs = ["FileOpNodeMemoizingLookup.java"],
deps = [
":value_or_future_map",
"//src/main/java/com/google/devtools/build/lib/actions:action_lookup_key",
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
"//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
"//src/main/java/com/google/devtools/build/lib/skyframe:filesystem_keys",
"//src/main/java/com/google/devtools/build/lib/skyframe:rule_configured_target_value",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:guava",
],
)

java_library(
name = "file_dependency_serializer",
srcs = ["FileDependencySerializer.java"],
Expand Down
Loading

0 comments on commit b704fdb

Please sign in to comment.