// 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.collect.nestedset; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.devtools.build.lib.actions.CommandLineItem; import com.google.devtools.build.lib.actions.CommandLineItem.MapFn; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.vfs.DigestHashFunction; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** Computes fingerprints for nested sets, reusing sub-computations from children. */ public class NestedSetFingerprintCache { private static final int EMPTY_SET_DIGEST = 104_395_303; /** Memoize the subresults. We have to have one cache per type of command item map function. */ private Map, DigestMap> mapFnToDigestMap = createMap(); private final Set> seenMapFns = new HashSet<>(); private final Multiset> seenParametrizedMapFns = HashMultiset.create(); public void addNestedSetToFingerprint(Fingerprint fingerprint, NestedSet nestedSet) { addNestedSetToFingerprint(CommandLineItem.MapFn.DEFAULT, fingerprint, nestedSet); } public void addNestedSetToFingerprint( CommandLineItem.MapFn mapFn, Fingerprint fingerprint, NestedSet nestedSet) { if (mapFn instanceof CommandLineItem.CapturingMapFn) { addNestedSetToFingerprintSlow(mapFn, fingerprint, nestedSet); return; } // Only top-level nested sets can be empty, so we can bail here if (nestedSet.isEmpty()) { fingerprint.addInt(EMPTY_SET_DIGEST); return; } DigestMap digestMap = mapFnToDigestMap.computeIfAbsent(mapFn, this::newDigestMap); fingerprint.addInt(nestedSet.getOrder().ordinal()); Object children = nestedSet.getChildren(); addToFingerprint(mapFn, fingerprint, digestMap, children); } private void addNestedSetToFingerprintSlow( MapFn mapFn, Fingerprint fingerprint, NestedSet nestedSet) { for (T object : nestedSet) { mapFn.expandToCommandLine(object, fingerprint); } } public void clear() { mapFnToDigestMap = createMap(); seenMapFns.clear(); seenParametrizedMapFns.clear(); } @SuppressWarnings("unchecked") private void addToFingerprint( CommandLineItem.MapFn mapFn, Fingerprint fingerprint, DigestMap digestMap, Object children) { if (children instanceof Object[]) { if (!digestMap.readDigest(children, fingerprint)) { Fingerprint childrenFingerprint = new Fingerprint(); for (Object child : (Object[]) children) { addToFingerprint(mapFn, childrenFingerprint, digestMap, child); } digestMap.insertAndReadDigest(children, childrenFingerprint, fingerprint); } } else { addToFingerprint(mapFn, fingerprint, (T) children); } } @VisibleForTesting void addToFingerprint( CommandLineItem.MapFn mapFn, Fingerprint fingerprint, T object) { mapFn.expandToCommandLine(object, fingerprint); } private static Map, DigestMap> createMap() { return new ConcurrentHashMap<>(); } private DigestMap newDigestMap(CommandLineItem.MapFn mapFn) { Class mapFnClass = mapFn.getClass(); if (mapFn instanceof CommandLineItem.ParametrizedMapFn) { int occurrences = seenParametrizedMapFns.add(mapFnClass, 1) + 1; if (occurrences > ((CommandLineItem.ParametrizedMapFn) mapFn).maxInstancesAllowed()) { throw new IllegalArgumentException( String.format( "Too many instances of CommandLineItem.ParametrizedMapFn '%s' detected. " + "Please construct fewer instances or use CommandLineItem.CapturingMapFn.", mapFnClass.getName())); } } else { if (!seenMapFns.add(mapFnClass)) { throw new IllegalArgumentException( String.format( "Illegal mapFn implementation: '%s'. " + "CommandLineItem.MapFn instances must be singletons. " + "Please see CommandLineItem.ParametrizedMapFn or " + "CommandLineItem.CapturingMapFn for alternatives.", mapFnClass.getName())); } } // TODO(b/112460990): Use the value from DigestHashFunction.getDefault(), but check for // contention. return new DigestMap(DigestHashFunction.MD5, 1024); } }