Skip to content

Commit 1d03d53

Browse files
justinhorvitzcopybara-github
authored andcommitted
When freezing an AttributeContainer, do not store non-explicit values that match the attribute's default value.
This saves a significant amount of space. Consumers of a frozen `AttributeContainer` must retrieve defaults from the `RuleClass` - this was already the case for regular defaults and is now also done for `ComputedDefault` values as well. Overhaul `AttributeContainerTest` to use `TestParameterInjector` to run tests with both small/large container sizes and mutable/frozen. PiperOrigin-RevId: 510248106 Change-Id: I4fd66a0afc576038021ffa1aa52c4fafb0571f67
1 parent 836c608 commit 1d03d53

File tree

6 files changed

+307
-227
lines changed

6 files changed

+307
-227
lines changed

src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,13 @@ private <T> void visitLabels(
136136
return;
137137
}
138138
rawVal = attributeContainer.getAttributeValue(i);
139-
if (rawVal == null && !attr.hasComputedDefault()) {
140-
rawVal = attr.getDefaultValue(null);
139+
if (rawVal == null) {
140+
if (!attr.hasComputedDefault()) {
141+
rawVal = attr.getDefaultValue(null);
142+
} else if (attributeContainer.isFrozen()) {
143+
// Frozen attribute containers don't store computed defaults.
144+
rawVal = attr.getDefaultValue(rule);
145+
}
141146
}
142147
if (rawVal instanceof SelectorList) {
143148
visitLabelsInSelect(

src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java

Lines changed: 83 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,20 @@ abstract class AttributeContainer {
6363

6464
/** Returns a frozen AttributeContainer with the same attributes, and a compact representation. */
6565
@CheckReturnValue
66-
abstract AttributeContainer freeze();
66+
abstract AttributeContainer freeze(Rule rule);
67+
68+
/**
69+
* Returns {@code true} if this container is immutable.
70+
*
71+
* <p>Frozen containers optimize for space by omitting storage for non-explicit attribute values
72+
* that match the {@link Attribute} default. If {@link #getAttributeValue} returns {@code null},
73+
* the value should be taken from {@link Attribute#getDefaultValue}, even for computed defaults.
74+
*
75+
* <p>Mutable containers have no such optimization. During rule creation, this allows for
76+
* distinguishing whether a computed default (which may depend on other unset attributes) is
77+
* available.
78+
*/
79+
abstract boolean isFrozen();
6780

6881
/** Returns an AttributeContainer for holding attributes of the given rule class. */
6982
static AttributeContainer newMutableInstance(RuleClass ruleClass) {
@@ -73,20 +86,19 @@ static AttributeContainer newMutableInstance(RuleClass ruleClass) {
7386
}
7487

7588
/** An AttributeContainer to which attributes may be added. */
76-
static final class Mutable extends AttributeContainer {
89+
private static final class Mutable extends AttributeContainer {
7790

7891
// Sparsely populated array of values, indexed by Attribute.index.
7992
final Object[] values;
80-
final BitSet explicitAttrs = new BitSet();
93+
final BitSet explicitIndices = new BitSet();
8194

82-
@VisibleForTesting
8395
Mutable(int maxAttrCount) {
8496
values = new Object[maxAttrCount];
8597
}
8698

8799
@Override
88100
public boolean isAttributeValueExplicitlySpecified(int attrIndex) {
89-
return (attrIndex >= 0) && explicitAttrs.get(attrIndex);
101+
return (attrIndex >= 0) && explicitIndices.get(attrIndex);
90102
}
91103

92104
/**
@@ -112,23 +124,45 @@ void setAttributeValue(int attrIndex, Object value, boolean explicit) {
112124
throw new IllegalArgumentException(
113125
"attribute with index " + attrIndex + " is not valid for rule");
114126
}
115-
if (!explicit && explicitAttrs.get(attrIndex)) {
127+
if (!explicit && explicitIndices.get(attrIndex)) {
116128
throw new IllegalArgumentException(
117129
"attribute with index " + attrIndex + " already explicitly set");
118130
}
119131
values[attrIndex] = value;
120132
if (explicit) {
121-
explicitAttrs.set(attrIndex);
133+
explicitIndices.set(attrIndex);
122134
}
123135
}
124136

125137
@Override
126-
public AttributeContainer freeze() {
127-
if (values.length < 126) {
128-
return new Small(values, explicitAttrs);
129-
} else {
130-
return new Large(values, explicitAttrs);
138+
public AttributeContainer freeze(Rule rule) {
139+
BitSet indicesToStore = new BitSet();
140+
RuleClass ruleClass = rule.getRuleClassObject();
141+
142+
for (int i = 0; i < values.length; i++) {
143+
Object value = values[i];
144+
if (value == null) {
145+
continue;
146+
}
147+
if (!explicitIndices.get(i)) {
148+
Attribute attr = ruleClass.getAttribute(i);
149+
Object defaultValue = attr.getDefaultValue(attr.hasComputedDefault() ? rule : null);
150+
if (value.equals(defaultValue)) {
151+
// Non-explicit value matches the attribute's default. Save space by omitting storage.
152+
continue;
153+
}
154+
}
155+
indicesToStore.set(i);
131156
}
157+
158+
return values.length < 126
159+
? new Small(values, explicitIndices, indicesToStore)
160+
: new Large(values, explicitIndices, indicesToStore);
161+
}
162+
163+
@Override
164+
public boolean isFrozen() {
165+
return false;
132166
}
133167

134168
@Override
@@ -148,24 +182,14 @@ final void setAttributeValue(int attrIndex, Object value, boolean explicit) {
148182
}
149183

150184
@Override
151-
final AttributeContainer freeze() {
185+
final AttributeContainer freeze(Rule rule) {
152186
return this;
153187
}
154-
}
155-
156-
private static final byte[] EMPTY_STATE = {};
157-
private static final Object[] EMPTY_VALUES = {};
158188

159-
/** Returns number of non-null values. */
160-
private static int nonNullCount(Object[] attrValues) {
161-
// Pre-allocate longer array.
162-
int numSet = 0;
163-
for (Object val : attrValues) {
164-
if (val != null) {
165-
numSet++;
166-
}
189+
@Override
190+
final boolean isFrozen() {
191+
return true;
167192
}
168-
return numSet;
169193
}
170194

171195
/** Returns index into state array for attrIndex, or -1 if not found */
@@ -209,7 +233,7 @@ static final class Small extends Frozen {
209233

210234
// The 'value' and 'explicit' components are encoded in the same byte.
211235
// Since this class only supports ruleClass with < 126 attributes,
212-
// state[i] encodes the the 'value' index in the 7 lower bits and 'explicit' in the top bit.
236+
// state[i] encodes the 'value' index in the 7 lower bits and 'explicit' in the top bit.
213237
// This is the common case.
214238
private final byte[] state;
215239

@@ -222,33 +246,27 @@ static final class Small extends Frozen {
222246
/**
223247
* Creates a container for a rule of the given rule class. Assumes attrIndex < 126 always.
224248
*
225-
* @param attrValues values for all attributes, null values are considered unset.
226-
* @param explicitAttrs holds explicit bit for each attribute index
249+
* @param attrValues values for all attributes, null values are considered unset
250+
* @param explicitIndices holds explicit bit for each attribute index
251+
* @param indicesToStore attribute indices for values that need to be stored, i.e., they were
252+
* explicitly set and/or differ from the attribute's default value
227253
*/
228-
private Small(Object[] attrValues, BitSet explicitAttrs) {
229-
maxAttrCount = attrValues.length;
230-
int numSet = nonNullCount(attrValues);
231-
if (numSet == 0) {
232-
this.values = EMPTY_VALUES;
233-
this.state = EMPTY_STATE;
234-
return;
235-
}
236-
values = new Object[numSet];
237-
state = new byte[numSet];
238-
int index = 0;
239-
int attrIndex = -1;
240-
for (Object attrValue : attrValues) {
241-
attrIndex++;
242-
if (attrValue == null) {
243-
continue;
244-
}
254+
private Small(Object[] attrValues, BitSet explicitIndices, BitSet indicesToStore) {
255+
this.maxAttrCount = attrValues.length;
256+
int numToStore = indicesToStore.cardinality();
257+
this.values = new Object[numToStore];
258+
this.state = new byte[numToStore];
259+
260+
int attrIndex = 0;
261+
for (int i = 0; i < numToStore; i++) {
262+
attrIndex = indicesToStore.nextSetBit(attrIndex);
245263
byte stateValue = (byte) (0x7f & attrIndex);
246-
if (explicitAttrs.get(attrIndex)) {
264+
if (explicitIndices.get(attrIndex)) {
247265
stateValue = (byte) (stateValue | 0x80);
248266
}
249-
state[index] = stateValue;
250-
values[index] = attrValue;
251-
index += 1;
267+
state[i] = stateValue;
268+
values[i] = attrValues[attrIndex];
269+
attrIndex++;
252270
}
253271
}
254272

@@ -353,32 +371,26 @@ private static boolean getExplicitBit(byte[] bytes, int attrIndex) {
353371
* Creates a container for a rule of the given rule class. Assumes maxAttrCount < 254
354372
*
355373
* @param attrValues values for all attributes, null values are considered unset.
356-
* @param explicitAttrs holds explicit bit for each attribute index
374+
* @param explicitIndices holds explicit bit for each attribute index
375+
* @param indicesToStore attribute indices for values that need to be stored, i.e. they were
376+
* explicitly set and/or differ from the attribute's default value
357377
*/
358-
private Large(Object[] attrValues, BitSet explicitAttrs) {
378+
private Large(Object[] attrValues, BitSet explicitIndices, BitSet indicesToStore) {
359379
this.maxAttrCount = attrValues.length;
360-
int numSet = nonNullCount(attrValues);
361-
if (numSet == 0) {
362-
this.values = EMPTY_VALUES;
363-
this.state = EMPTY_STATE;
364-
return;
365-
}
380+
int numToStore = indicesToStore.cardinality();
366381
int p = prefixSize(maxAttrCount);
367-
values = new Object[numSet];
368-
state = new byte[p + numSet];
369-
int index = 0;
370-
int attrIndex = -1;
371-
for (Object attrValue : attrValues) {
372-
attrIndex++;
373-
if (attrValue == null) {
374-
continue;
375-
}
376-
if (explicitAttrs.get(attrIndex)) {
382+
this.values = new Object[numToStore];
383+
this.state = new byte[p + numToStore];
384+
385+
int attrIndex = 0;
386+
for (int i = 0; i < numToStore; i++) {
387+
attrIndex = indicesToStore.nextSetBit(attrIndex);
388+
if (explicitIndices.get(attrIndex)) {
377389
setExplicitBit(state, attrIndex);
378390
}
379-
state[index + p] = (byte) attrIndex;
380-
values[index] = attrValue;
381-
index += 1;
391+
state[i + p] = (byte) attrIndex;
392+
values[i] = attrValues[attrIndex];
393+
attrIndex++;
382394
}
383395
}
384396

src/main/java/com/google/devtools/build/lib/packages/Rule.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -443,12 +443,13 @@ private Object getAttrWithIndex(int attrIndex) {
443443
}
444444
Attribute attr = ruleClass.getAttribute(attrIndex);
445445
if (attr.hasComputedDefault()) {
446-
// Attributes with computed defaults are explicitly populated during rule creation.
447-
// However, computing those defaults could trigger reads of other attributes
448-
// which have not yet been populated. In such a case control comes here, and we return null.
449-
// NOTE: In this situation returning null does not result in a correctness issue, since
450-
// the value for the attribute is actually a function to compute the value.
451-
return null;
446+
// Frozen attribute containers don't store computed defaults, so get it from the attribute.
447+
// Mutable attribute containers do store computed defaults if they've been populated. If a
448+
// mutable container returns null, return null here since resolving the default could trigger
449+
// reads of other attributes which have not yet been populated. Note that in this situation
450+
// returning null does not result in a correctness issue, since the value for the attribute is
451+
// actually a function to compute the value.
452+
return attributes.isFrozen() ? attr.getDefaultValue(this) : null;
452453
}
453454
switch (attr.getName()) {
454455
case GENERATOR_FUNCTION:
@@ -633,7 +634,7 @@ public Multimap<Attribute, Label> getTransitions(DependencyFilter filter) {
633634
}
634635

635636
void freeze() {
636-
attributes = attributes.freeze();
637+
attributes = attributes.freeze(this);
637638
}
638639

639640
/**

0 commit comments

Comments
 (0)