In Parser, dedupe the String instances referred to by StringLiteral instances. Implement the same optimization in the serialization code.

Some .bzl files have tons of duplicate string constants.

We can't dedupe the StringLiteral instances themselves, because they all have different Locations.

RELNOTES: None
PiperOrigin-RevId: 232495067
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index dea8f33..000c6de 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -19,7 +19,9 @@
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Interner;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.concurrent.BlazeInterners;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.Location;
@@ -164,6 +166,8 @@
   private int errorsCount;
   private boolean recoveryMode;  // stop reporting errors until next statement
 
+  private final Interner<String> stringInterner = BlazeInterners.newStrongInterner();
+
   private Parser(Lexer lexer, EventHandler eventHandler) {
     this.lexer = lexer;
     this.eventHandler = eventHandler;
@@ -599,7 +603,8 @@
     Preconditions.checkState(token.kind == TokenKind.STRING);
     int end = token.right;
     StringLiteral literal =
-        setLocation(new StringLiteral((String) token.value), token.left, end);
+        setLocation(
+            new StringLiteral(stringInterner.intern((String) token.value)), token.left, end);
 
     nextToken();
     if (token.kind == TokenKind.STRING) {
@@ -958,7 +963,7 @@
       if (expr instanceof StringLiteral && secondary instanceof StringLiteral) {
         StringLiteral left = (StringLiteral) expr;
         StringLiteral right = (StringLiteral) secondary;
-        return new StringLiteral(left.getValue() + right.getValue());
+        return new StringLiteral(stringInterner.intern(left.getValue() + right.getValue()));
       }
     }
     return new BinaryOperatorExpression(operator, expr, secondary);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java b/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
index b88bbb1..f09d87d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StringLiteral.java
@@ -13,6 +13,13 @@
 // limitations under the License.
 package com.google.devtools.build.lib.syntax;
 
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
 import java.io.IOException;
 
 /** Syntax node for a string literal. */
@@ -46,4 +53,34 @@
   public Kind kind() {
     return Kind.STRING_LITERAL;
   }
+
+  static final class StringLiteralCodec implements ObjectCodec<StringLiteral> {
+    @Override
+    public Class<? extends StringLiteral> getEncodedClass() {
+      return StringLiteral.class;
+    }
+
+    @Override
+    public void serialize(
+        SerializationContext context, StringLiteral stringLiteral, CodedOutputStream codedOut)
+        throws SerializationException, IOException {
+      // The String instances referred to by StringLiterals are deduped by Parser, so therefore
+      // memoization is guaranteed to be profitable.
+      context.serializeWithAdHocMemoizationStrategy(
+          stringLiteral.getValue(), MemoizationStrategy.MEMOIZE_AFTER, codedOut);
+      context.serialize(stringLiteral.getLocation(), codedOut);
+    }
+
+    @Override
+    public StringLiteral deserialize(DeserializationContext context, CodedInputStream codedIn)
+        throws SerializationException, IOException {
+      String value =
+          context.deserializeWithAdHocMemoizationStrategy(
+              codedIn, MemoizationStrategy.MEMOIZE_AFTER);
+      Location location = context.deserialize(codedIn);
+      StringLiteral stringLiteral = new StringLiteral(value);
+      stringLiteral.setLocation(location);
+      return stringLiteral;
+    }
+  }
 }