Optimize for the common case of `generator_name` being a prefix of a rule's name.

There's a free field in `Rule`. Use this to store the prefix length and avoid storing the string as a raw attribute value. This is similar to other optimizations already made for `generator_function` and `generator_location`.

More details in the Javadoc on `Rule#generatorNamePrefixLength`.

PiperOrigin-RevId: 522703163
Change-Id: I2017545667fad07e94c178df59400741b43366c7
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Rule.java b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
index 52b5826..c338f08 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Rule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
@@ -82,6 +82,7 @@
   private static final String NAME = RuleClass.NAME_ATTRIBUTE.getName();
   private static final String GENERATOR_FUNCTION = "generator_function";
   private static final String GENERATOR_LOCATION = "generator_location";
+  private static final String GENERATOR_NAME = "generator_name";
 
   private static final int ATTR_SIZE_THRESHOLD = 126;
 
@@ -94,6 +95,26 @@
   @Nullable private final CallStack.Node interiorCallStack;
 
   /**
+   * The length of this rule's generator name if it is a prefix of its name, otherwise zero.
+   *
+   * <p>The generator name of a rule is the {@code name} parameter passed to a macro that
+   * instantiates the rule. Most rules instantiated via macro follow this pattern:
+   *
+   * <pre>{@code
+   * def some_macro(name):
+   *   some_rule(name = name + '_some_suffix')
+   * }</pre>
+   *
+   * thus resulting in a generator name which is a prefix of the rule name. In such a case, we save
+   * memory by storing the length of the generator name instead of the string. Note that this saves
+   * memory from both the storage in {@link #attrValues} and the string itself (if it is not
+   * otherwise retained). This optimization works because this field does not push the shallow heap
+   * cost of {@link Rule} beyond an 8-byte threshold. If it did, this optimization would be a net
+   * loss.
+   */
+  private int generatorNamePrefixLength = 0;
+
+  /**
    * Stores attribute values, taking on one of two shapes:
    *
    * <ol>
@@ -452,6 +473,13 @@
       // Avoid unnecessarily storing the name in attrValues - it's stored in the label.
       return;
     }
+    if (attrName.equals(GENERATOR_NAME)) {
+      String generatorName = (String) value;
+      if (getName().startsWith(generatorName)) {
+        generatorNamePrefixLength = generatorName.length();
+        return;
+      }
+    }
     Integer attrIndex = ruleClass.getAttributeIndex(attrName);
     checkArgument(attrIndex != null, "Attribute %s is not valid for this rule", attrName);
     if (explicit) {
@@ -527,6 +555,10 @@
         return interiorCallStack != null ? interiorCallStack.functionName() : "";
       case GENERATOR_LOCATION:
         return interiorCallStack != null ? getRelativeLocation() : "";
+      case GENERATOR_NAME:
+        return generatorNamePrefixLength > 0
+            ? getName().substring(0, generatorNamePrefixLength)
+            : "";
       default:
         return attr.getDefaultValue();
     }
@@ -588,7 +620,9 @@
     if (attrName.equals(NAME)) {
       return true;
     }
-    if (attrName.equals(GENERATOR_FUNCTION) || attrName.equals(GENERATOR_LOCATION)) {
+    if (attrName.equals(GENERATOR_FUNCTION)
+        || attrName.equals(GENERATOR_LOCATION)
+        || attrName.equals(GENERATOR_NAME)) {
       return wasCreatedByMacro();
     }
     Integer attrIndex = ruleClass.getAttributeIndex(attrName);
@@ -800,7 +834,7 @@
    * Returns whether this rule was created by a macro.
    */
   public boolean wasCreatedByMacro() {
-    return interiorCallStack != null || hasStringAttribute("generator_name");
+    return interiorCallStack != null || hasStringAttribute(GENERATOR_NAME);
   }
 
   /** Returns the macro that generated this rule, or an empty string. */