|
18 | 18 | import com.google.common.base.Preconditions; |
19 | 19 | import com.google.common.collect.ImmutableList; |
20 | 20 | import com.google.devtools.build.lib.cmdline.Label; |
| 21 | +import com.google.devtools.build.lib.collect.nestedset.Depset; |
| 22 | +import com.google.devtools.build.lib.collect.nestedset.Depset.ElementType; |
| 23 | +import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
21 | 24 | import com.google.devtools.build.lib.events.EventHandler; |
22 | 25 | import com.google.devtools.build.lib.util.Fingerprint; |
23 | 26 | import com.google.errorprone.annotations.CanIgnoreReturnValue; |
24 | 27 | import java.util.Collection; |
25 | 28 | import java.util.Map; |
26 | 29 | import java.util.Objects; |
| 30 | +import java.util.concurrent.atomic.AtomicReferenceArray; |
27 | 31 | import javax.annotation.Nullable; |
28 | 32 | import net.starlark.java.eval.Dict; |
29 | 33 | import net.starlark.java.eval.EvalException; |
@@ -69,6 +73,32 @@ public final class StarlarkProvider implements StarlarkCallable, StarlarkExporta |
69 | 73 | /** Null iff this provider has not yet been exported. Mutated by {@link export}. */ |
70 | 74 | @Nullable private Key key; |
71 | 75 |
|
| 76 | + /** |
| 77 | + * For schemaful providers, an array of metadata concerning depset optimization. |
| 78 | + * |
| 79 | + * <p>Each index in the array holds an optional (nullable) depset element type. The value at that |
| 80 | + * index is initialized to be the element type of the first non-empty Depset to ever be stored in |
| 81 | + * the corresponding field from {@link #schema} on any instance of this provider, globally. If no |
| 82 | + * depsets (or only empty depsets) are ever stored in a field, the value at its index in this |
| 83 | + * array will remain null. |
| 84 | + * |
| 85 | + * <p>Whenever a field is stored in an instance of this provider type, if the value is a depset |
| 86 | + * whose element type matches the one stored in this array, it is optimized by unwrapping it down |
| 87 | + * to its {@code NestedSet}. Upon retrieval, the depset wrapper is reconstructed using this saved |
| 88 | + * element type. |
| 89 | + * |
| 90 | + * <p>The optimization may (harmlessly) fail to apply for provider fields that are not strongly |
| 91 | + * typed across all instances. |
| 92 | + * |
| 93 | + * <p>For large builds, this optimization has been observed to save half a percent in retained |
| 94 | + * heap. |
| 95 | + * |
| 96 | + * <p>In the future, the ad hoc heuristic of examining the first stored non-empty depset might be |
| 97 | + * replaced by stronger type information in the provider's Starlark declaration. However, this |
| 98 | + * optimization would remain relevant for provider declarations that do not supply such type info. |
| 99 | + */ |
| 100 | + @Nullable private transient AtomicReferenceArray<Class<?>> depsetTypePredictor; |
| 101 | + |
72 | 102 | /** |
73 | 103 | * Returns a new empty builder. |
74 | 104 | * |
@@ -157,6 +187,9 @@ private StarlarkProvider( |
157 | 187 | this.schema = schema; |
158 | 188 | this.init = init; |
159 | 189 | this.key = key; |
| 190 | + if (schema != null) { |
| 191 | + depsetTypePredictor = new AtomicReferenceArray<>(schema.size()); |
| 192 | + } |
160 | 193 | } |
161 | 194 |
|
162 | 195 | private static Object[] toNamedArgs(Object value, String descriptionForError) |
@@ -323,6 +356,67 @@ public String toString() { |
323 | 356 | return Starlark.repr(this); |
324 | 357 | } |
325 | 358 |
|
| 359 | + /** |
| 360 | + * For schemaful providers, given a value to store in the field identified by {@code index}, |
| 361 | + * returns a possibly optimized version of the value. The result (optimized or not) should be |
| 362 | + * decoded by {@link #retrieveOptimizedField}. |
| 363 | + * |
| 364 | + * <p>Mutable values are never optimized. |
| 365 | + */ |
| 366 | + Object optimizeField(int index, Object value) { |
| 367 | + if (value instanceof Depset) { |
| 368 | + Preconditions.checkArgument(depsetTypePredictor != null); |
| 369 | + Depset depset = (Depset) value; |
| 370 | + if (depset.isEmpty()) { |
| 371 | + // Most empty depsets have the empty (null) type. We can't store this type because it |
| 372 | + // would clash with whatever the actual element type is for non-empty depsets in that |
| 373 | + // field. So instead just store the optimized (unwrapped) NestedSet without any type |
| 374 | + // information, and assume it's the empty type upon retrieval. |
| 375 | + // |
| 376 | + // This only loses information in the relatively rare case of a native-constructed empty |
| 377 | + // depset with a type restriction (e.g. empty set of artifacts). In that scenario, an |
| 378 | + // empty depset retrieved from the provider may "incorrectly" allow itself to participate |
| 379 | + // in a union with depsets of other types, whereas the original depset would trigger a |
| 380 | + // Starlark eval error. This is a user-observable difference but a very minor one; the |
| 381 | + // hazard would be logical errors that are masked by the provider machinery but triggered |
| 382 | + // by a refactoring of Starlark code. See TODO in Depset#of(Class, NestedSet) for notes |
| 383 | + // about eliminating this semantic confusion. |
| 384 | + // |
| 385 | + // This problem shouldn't arise for non-empty depsets since distinct non-empty element |
| 386 | + // types are not compatible with one another (i.e. there's no Depset<Any> schema). |
| 387 | + return depset.getSet(); |
| 388 | + } |
| 389 | + Class<?> elementClass = depset.getElementClass(); |
| 390 | + if (depsetTypePredictor.compareAndExchange(index, null, elementClass) == elementClass) { |
| 391 | + return depset.getSet(); |
| 392 | + } |
| 393 | + } |
| 394 | + return value; |
| 395 | + } |
| 396 | + |
| 397 | + Object retrieveOptimizedField(int index, Object value) { |
| 398 | + if (value instanceof NestedSet<?>) { |
| 399 | + // We subvert Depset.of()'s static type checking for consistency between the type token and |
| 400 | + // NestedSet type. This is safe because these values came from a previous Depset, so we |
| 401 | + // already know they're consistent. |
| 402 | + @SuppressWarnings("unchecked") |
| 403 | + NestedSet<Object> nestedSet = (NestedSet<Object>) value; |
| 404 | + if (nestedSet.isEmpty()) { |
| 405 | + // This matches empty depsets created in Starlark with `depset()`. For natively created |
| 406 | + // empty depsets it may change elementClass to null. |
| 407 | + return Depset.of(ElementType.EMPTY, nestedSet); |
| 408 | + } |
| 409 | + @SuppressWarnings("unchecked") // can't parametrize Class literal by a non-raw type |
| 410 | + Depset depset = Depset.of((Class<Object>) depsetTypePredictor.get(index), nestedSet); |
| 411 | + return depset; |
| 412 | + } |
| 413 | + return value; |
| 414 | + } |
| 415 | + |
| 416 | + boolean isOptimised(int index, Object value) { |
| 417 | + return value instanceof NestedSet<?>; |
| 418 | + } |
| 419 | + |
326 | 420 | /** |
327 | 421 | * A serializable representation of Starlark-defined {@link StarlarkProvider} that uniquely |
328 | 422 | * identifies all {@link StarlarkProvider}s that are exposed to SkyFrame. |
|
0 commit comments