blob: 99c8e0cb8a8c5de8371fe50fe668fd39ab370101 [file] [log] [blame]
// Copyright 2018 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.actions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* Helper for {@link MetadataProvider} implementations.
*
* <p>Allows {@link FileArtifactValue} lookups by exec path or {@link ActionInput}. <i>Also</i>
* allows {@link ActionInput} to be looked up by exec path.
*
* <p>This class implements a closed hash-map with the "links" of each bucket's linked list being
* stored in a flat array to avoid memory allocations and garbage collection.
*
* <p>This class is thread-compatible.
*/
public final class ActionInputMap implements MetadataProvider, ActionInputMapSink {
/** The number of elements contained in this map. */
int size;
/**
* The hash buckets. Values are indexes into the four arrays. The number of buckets is always the
* smallest power of 2 that is larger than the number of elements.
*/
private int[] table;
/** Flat array of the next pointers that make up the linked list behind each hash bucket. */
private int[] next;
/**
* The {@link ActionInput} keys stored in this map. For performance reasons, they need to be
* stored as {@link Object}s as otherwise, the JVM does not seem to be as good optimizing the
* store operations (maybe it does checks on the type being stored?).
*/
private Object[] keys;
/**
* Extra storage for the execPathStrings of the values in {@link #keys}. This extra storage is
* necessary for speed as otherwise, we'd need to cast to {@link ActionInput}, which is slow.
*/
private Object[] paths;
/**
* The {@link FileArtifactValue} data stored in this map. Same as the other arrays, this is stored
* as {@link Object} for performance reasons.
*/
private Object[] values;
public ActionInputMap(int sizeHint) {
sizeHint = Math.max(1, sizeHint);
int tableSize = Integer.highestOneBit(sizeHint) << 1;
size = 0;
table = new int[tableSize];
Arrays.fill(table, -1);
next = new int[sizeHint];
keys = new Object[sizeHint];
paths = new Object[sizeHint];
values = new Object[sizeHint];
}
private int getIndex(String execPathString) {
int hashCode = execPathString.hashCode();
int index = hashCode & (table.length - 1);
if (table[index] == -1) {
return -1;
}
index = table[index];
while (index != -1) {
if (hashCode == paths[index].hashCode() && execPathString.equals(paths[index])) {
return index;
}
index = next[index];
}
return -1;
}
@Nullable
@Override
public FileArtifactValue getMetadata(ActionInput input) {
return getMetadata(input.getExecPathString());
}
@Nullable
public FileArtifactValue getMetadata(String execPathString) {
int index = getIndex(execPathString);
return index == -1 ? null : (FileArtifactValue) values[index];
}
@Nullable
@Override
public ActionInput getInput(String execPathString) {
int index = getIndex(execPathString);
return index == -1 ? null : (ActionInput) keys[index];
}
/** Count of contained entries. */
public int size() {
return size;
}
@Override
public boolean put(ActionInput input, FileArtifactValue metadata, @Nullable Artifact depOwner) {
return putWithNoDepOwner(input, metadata);
}
public boolean putWithNoDepOwner(ActionInput input, FileArtifactValue metadata) {
Preconditions.checkNotNull(input);
if (size >= keys.length) {
resize();
}
String path = input.getExecPathString();
int hashCode = path.hashCode();
int index = hashCode & (table.length - 1);
int nextIndex = table[index];
if (nextIndex == -1) {
table[index] = size;
} else {
do {
index = nextIndex;
if (hashCode == paths[index].hashCode() && Objects.equal(path, paths[index])) {
return false;
}
nextIndex = next[index];
} while (nextIndex != -1);
next[index] = size;
}
next[size] = -1;
keys[size] = input;
paths[size] = input.getExecPathString();
values[size] = metadata;
size++;
return true;
}
@VisibleForTesting
void clear() {
Arrays.fill(table, -1);
Arrays.fill(next, -1);
Arrays.fill(keys, null);
Arrays.fill(paths, null);
Arrays.fill(values, null);
size = 0;
}
private void resize() {
// Resize the data containers.
keys = Arrays.copyOf(keys, size * 2);
paths = Arrays.copyOf(paths, size * 2);
values = Arrays.copyOf(values, size * 2);
next = Arrays.copyOf(next, size * 2);
// Resize and recreate the table and links if necessary. We can take shortcuts here as we know
// there are no duplicate keys.
if (table.length < size * 2) {
table = new int[table.length * 2];
next = new int[size * 2];
Arrays.fill(table, -1);
for (int i = 0; i < size; i++) {
int index = paths[i].hashCode() & (table.length - 1);
next[i] = table[index];
table[index] = i;
}
}
}
}