Remove constant parameters from throw block outlines

Bug: b/434769547
Change-Id: Id4073c07f0d04b994bb03d5bb369ac0250993169
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 12b0e3b..d9e1cfd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.lightir.LirConstant;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.IntObjPredicate;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -168,4 +170,19 @@
   public void internalLirConstantAcceptHashing(HashingVisitor visitor) {
     acceptHashing(visitor);
   }
+
+  public DexProto withoutParameters(IntObjPredicate<DexType> predicate, DexItemFactory factory) {
+    if (parameters.isEmpty()) {
+      return this;
+    }
+    DexType[] newParameters =
+        ArrayUtils.map(
+            parameters.getBacking(),
+            (index, parameter) -> predicate.test(index, parameter) ? null : parameter,
+            DexType.EMPTY_ARRAY);
+    if (newParameters == parameters.getBacking()) {
+      return this;
+    }
+    return factory.createProto(returnType, newParameters);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
index 6b4b88f..8556a9f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.outliner.exceptions.ThrowBlockOutline;
 import com.android.tools.r8.lightir.LirBuilder;
+import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
 
 public class ThrowBlockOutlineMarker extends Instruction {
@@ -29,6 +30,28 @@
     return new Builder();
   }
 
+  // Removes the in-values from this outline marker where the corresponding outline parameter has
+  // been removed due to constant propagation.
+  public boolean detachConstantOutlineArguments(ThrowBlockOutline outline) {
+    List<Value> newArguments =
+        ListUtils.mapOrElse(
+            inValues,
+            (i, argument) -> {
+              if (outline.isArgumentConstant(i)) {
+                argument.removeUser(this);
+                return null;
+              }
+              return argument;
+            },
+            null);
+    if (newArguments != null) {
+      inValues.clear();
+      inValues.addAll(newArguments);
+      return true;
+    }
+    return false;
+  }
+
   public ThrowBlockOutline getOutline() {
     return outline;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java
new file mode 100644
index 0000000..9dff1d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeArgumentRewriter.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.proto.ArgumentInfo;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.MaterializingInstructionsInfo;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.ir.code.UnusedArgument;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.ArrayUtils;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class LensCodeArgumentRewriter {
+
+  private final AppView<?> appView;
+
+  public LensCodeArgumentRewriter(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  // Applies the prototype changes of the current method to the argument instructions:
+  // - Replaces constant arguments by their constant value and then removes the (now unused)
+  //   argument instruction
+  // - Removes unused arguments
+  // - Updates the type of arguments whose type has been strengthened
+  // TODO(b/270398965): Replace LinkedList.
+  @SuppressWarnings("JdkObsolete")
+  public void rewriteArguments(
+      IRCode code,
+      DexMethod originalMethodReference,
+      RewrittenPrototypeDescription prototypeChanges,
+      Set<Phi> affectedPhis,
+      Set<UnusedArgument> unusedArguments) {
+    AffectedValues affectedValues = new AffectedValues();
+    ArgumentInfoCollection argumentInfoCollection = prototypeChanges.getArgumentInfoCollection();
+    List<Instruction> argumentPostlude = new LinkedList<>();
+    int oldArgumentIndex = 0;
+    int nextArgumentIndex = 0;
+    int numberOfRemovedArguments = 0;
+    BasicBlock basicBlock = code.entryBlock();
+    InstructionListIterator instructionIterator = basicBlock.listIterator();
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      if (!instruction.isArgument()) {
+        break;
+      }
+
+      Argument argument = instruction.asArgument();
+      ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(oldArgumentIndex);
+      if (argumentInfo.isRemovedArgumentInfo()) {
+        rewriteRemovedArgument(
+            code,
+            instructionIterator,
+            originalMethodReference,
+            argument,
+            argumentInfo.asRemovedArgumentInfo(),
+            affectedPhis,
+            affectedValues,
+            argumentPostlude,
+            unusedArguments);
+        numberOfRemovedArguments++;
+      } else {
+        int newArgumentIndex =
+            argumentInfoCollection.getNewArgumentIndex(oldArgumentIndex, numberOfRemovedArguments);
+        Argument replacement;
+        if (argumentInfo.isRewrittenTypeInfo()) {
+          replacement =
+              rewriteArgumentType(
+                  code,
+                  argument,
+                  argumentInfo.asRewrittenTypeInfo(),
+                  affectedPhis,
+                  newArgumentIndex);
+          argument.outValue().replaceUsers(replacement.outValue());
+        } else if (newArgumentIndex != oldArgumentIndex) {
+          replacement =
+              Argument.builder()
+                  .setIndex(newArgumentIndex)
+                  .setFreshOutValue(code, argument.getOutType(), argument.getLocalInfo())
+                  .setPosition(argument.getPosition())
+                  .build();
+          argument.outValue().replaceUsers(replacement.outValue());
+        } else {
+          replacement = argument;
+        }
+        if (newArgumentIndex == nextArgumentIndex) {
+          // This is the right position for the argument. Insert it into the code at this position.
+          if (replacement != argument) {
+            instructionIterator.replaceCurrentInstruction(replacement);
+          }
+          nextArgumentIndex++;
+        } else {
+          // Due the a permutation of the argument order, this argument needs to be inserted at a
+          // later point. Enqueue the argument into the argument postlude.
+          instructionIterator.removeInstructionIgnoreOutValue();
+          ListIterator<Instruction> argumentPostludeIterator = argumentPostlude.listIterator();
+          while (argumentPostludeIterator.hasNext()) {
+            Instruction current = argumentPostludeIterator.next();
+            if (!current.isArgument()
+                || replacement.getIndexRaw() < current.asArgument().getIndexRaw()) {
+              argumentPostludeIterator.previous();
+              break;
+            }
+          }
+          argumentPostludeIterator.add(replacement);
+        }
+      }
+      oldArgumentIndex++;
+    }
+
+    instructionIterator.previous();
+
+    if (!argumentPostlude.isEmpty()) {
+      for (Instruction instruction : argumentPostlude) {
+        instructionIterator.add(instruction);
+      }
+    }
+
+    affectedValues.narrowingWithAssumeRemoval(appView, code);
+  }
+
+  private Argument rewriteArgumentType(
+      IRCode code,
+      Argument argument,
+      RewrittenTypeInfo rewrittenTypeInfo,
+      Set<Phi> affectedPhis,
+      int newArgumentIndex) {
+    TypeElement rewrittenType = rewrittenTypeInfo.getNewType().toTypeElement(appView);
+    Argument replacement =
+        Argument.builder()
+            .setIndex(newArgumentIndex)
+            .setFreshOutValue(code, rewrittenType, argument.getLocalInfo())
+            .setPosition(argument.getPosition())
+            .build();
+    affectedPhis.addAll(argument.outValue().uniquePhiUsers());
+    return replacement;
+  }
+
+  private void rewriteRemovedArgument(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      DexMethod originalMethodReference,
+      Argument argument,
+      RemovedArgumentInfo removedArgumentInfo,
+      Set<Phi> affectedPhis,
+      AffectedValues affectedValues,
+      List<Instruction> argumentPostlude,
+      Set<UnusedArgument> unusedArguments) {
+    Instruction[] replacement;
+    if (removedArgumentInfo.hasSingleValue()) {
+      SingleValue singleValue = removedArgumentInfo.getSingleValue();
+      TypeElement type =
+          removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
+              ? TypeElement.getNull()
+              : removedArgumentInfo.getType().toTypeElement(appView);
+      Position position =
+          SourcePosition.builder().setLine(0).setMethod(originalMethodReference).build();
+      replacement =
+          singleValue.createMaterializingInstructions(
+              appView,
+              code,
+              MaterializingInstructionsInfo.create(type, argument.getLocalInfo(), position));
+    } else {
+      TypeElement unusedArgumentType = removedArgumentInfo.getType().toTypeElement(appView);
+      UnusedArgument unusedArgument =
+          UnusedArgument.builder()
+              .setFreshOutValue(code, unusedArgumentType)
+              .setPosition(Position.none())
+              .build();
+      unusedArguments.add(unusedArgument);
+      replacement = new Instruction[] {unusedArgument};
+    }
+    Value replacementValue = ArrayUtils.last(replacement).outValue();
+    argument.outValue().replaceUsers(replacementValue, affectedValues);
+    affectedPhis.addAll(replacementValue.uniquePhiUsers());
+    Collections.addAll(argumentPostlude, replacement);
+    instructionIterator.removeOrReplaceByDebugLocalRead();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 07bef0b..aef1339 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -56,7 +56,6 @@
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfo;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
-import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -64,8 +63,6 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
-import com.android.tools.r8.ir.analysis.value.SingleValue;
-import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
@@ -99,7 +96,6 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.SafeCheckCast;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -121,13 +117,10 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -163,10 +156,13 @@
   private final DexItemFactory factory;
   private final InternalOptions options;
 
+  private final LensCodeArgumentRewriter argumentRewriter;
+
   public LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
+    this.argumentRewriter = new LensCodeArgumentRewriter(appView);
   }
 
   private Value makeOutValue(
@@ -247,7 +243,7 @@
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     AffectedValues affectedValues = new AffectedValues();
     Set<UnusedArgument> unusedArguments = Sets.newIdentityHashSet();
-    rewriteArguments(
+    argumentRewriter.rewriteArguments(
         code, originalMethodReference, prototypeChanges, affectedPhis, unusedArguments);
     if (graphLens.hasCustomLensCodeRewriter()) {
       assert graphLens.getPrevious() == codeLens
@@ -921,147 +917,6 @@
     assert code.isConsistentSSABeforeTypesAreCorrect(appView);
   }
 
-  // Applies the prototype changes of the current method to the argument instructions:
-  // - Replaces constant arguments by their constant value and then removes the (now unused)
-  //   argument instruction
-  // - Removes unused arguments
-  // - Updates the type of arguments whose type has been strengthened
-  // TODO(b/270398965): Replace LinkedList.
-  @SuppressWarnings("JdkObsolete")
-  private void rewriteArguments(
-      IRCode code,
-      DexMethod originalMethodReference,
-      RewrittenPrototypeDescription prototypeChanges,
-      Set<Phi> affectedPhis,
-      Set<UnusedArgument> unusedArguments) {
-    AffectedValues affectedValues = new AffectedValues();
-    ArgumentInfoCollection argumentInfoCollection = prototypeChanges.getArgumentInfoCollection();
-    List<Instruction> argumentPostlude = new LinkedList<>();
-    int oldArgumentIndex = 0;
-    int nextArgumentIndex = 0;
-    int numberOfRemovedArguments = 0;
-    BasicBlock basicBlock = code.entryBlock();
-    InstructionListIterator instructionIterator = basicBlock.listIterator();
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
-      if (!instruction.isArgument()) {
-        break;
-      }
-
-      Argument argument = instruction.asArgument();
-      ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(oldArgumentIndex);
-      if (argumentInfo.isRemovedArgumentInfo()) {
-        rewriteRemovedArgument(
-            code,
-            instructionIterator,
-            originalMethodReference,
-            argument,
-            argumentInfo.asRemovedArgumentInfo(),
-            affectedPhis,
-            affectedValues,
-            argumentPostlude,
-            unusedArguments);
-        numberOfRemovedArguments++;
-      } else {
-        int newArgumentIndex =
-            argumentInfoCollection.getNewArgumentIndex(oldArgumentIndex, numberOfRemovedArguments);
-        Argument replacement;
-        if (argumentInfo.isRewrittenTypeInfo()) {
-          replacement =
-              rewriteArgumentType(
-                  code,
-                  argument,
-                  argumentInfo.asRewrittenTypeInfo(),
-                  affectedPhis,
-                  newArgumentIndex);
-          argument.outValue().replaceUsers(replacement.outValue());
-        } else if (newArgumentIndex != oldArgumentIndex) {
-          replacement =
-              Argument.builder()
-                  .setIndex(newArgumentIndex)
-                  .setFreshOutValue(code, argument.getOutType(), argument.getLocalInfo())
-                  .setPosition(argument.getPosition())
-                  .build();
-          argument.outValue().replaceUsers(replacement.outValue());
-        } else {
-          replacement = argument;
-        }
-        if (newArgumentIndex == nextArgumentIndex) {
-          // This is the right position for the argument. Insert it into the code at this position.
-          if (replacement != argument) {
-            instructionIterator.replaceCurrentInstruction(replacement);
-          }
-          nextArgumentIndex++;
-        } else {
-          // Due the a permutation of the argument order, this argument needs to be inserted at a
-          // later point. Enqueue the argument into the argument postlude.
-          instructionIterator.removeInstructionIgnoreOutValue();
-          ListIterator<Instruction> argumentPostludeIterator = argumentPostlude.listIterator();
-          while (argumentPostludeIterator.hasNext()) {
-            Instruction current = argumentPostludeIterator.next();
-            if (!current.isArgument()
-                || replacement.getIndexRaw() < current.asArgument().getIndexRaw()) {
-              argumentPostludeIterator.previous();
-              break;
-            }
-          }
-          argumentPostludeIterator.add(replacement);
-        }
-      }
-      oldArgumentIndex++;
-    }
-
-    instructionIterator.previous();
-
-    if (!argumentPostlude.isEmpty()) {
-      for (Instruction instruction : argumentPostlude) {
-        instructionIterator.add(instruction);
-      }
-    }
-
-    affectedValues.narrowingWithAssumeRemoval(appView, code);
-  }
-
-  private void rewriteRemovedArgument(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      DexMethod originalMethodReference,
-      Argument argument,
-      RemovedArgumentInfo removedArgumentInfo,
-      Set<Phi> affectedPhis,
-      AffectedValues affectedValues,
-      List<Instruction> argumentPostlude,
-      Set<UnusedArgument> unusedArguments) {
-    Instruction[] replacement;
-    if (removedArgumentInfo.hasSingleValue()) {
-      SingleValue singleValue = removedArgumentInfo.getSingleValue();
-      TypeElement type =
-          removedArgumentInfo.getType().isReferenceType() && singleValue.isNull()
-              ? TypeElement.getNull()
-              : removedArgumentInfo.getType().toTypeElement(appView);
-      Position position =
-          SourcePosition.builder().setLine(0).setMethod(originalMethodReference).build();
-      replacement =
-          singleValue.createMaterializingInstructions(
-              appView,
-              code,
-              MaterializingInstructionsInfo.create(type, argument.getLocalInfo(), position));
-    } else {
-      TypeElement unusedArgumentType = removedArgumentInfo.getType().toTypeElement(appView);
-      UnusedArgument unusedArgument =
-          UnusedArgument.builder()
-              .setFreshOutValue(code, unusedArgumentType)
-              .setPosition(Position.none())
-              .build();
-      unusedArguments.add(unusedArgument);
-      replacement = new Instruction[] {unusedArgument};
-    }
-    Value replacementValue = ArrayUtils.last(replacement).outValue();
-    argument.outValue().replaceUsers(replacementValue, affectedValues);
-    affectedPhis.addAll(replacementValue.uniquePhiUsers());
-    Collections.addAll(argumentPostlude, replacement);
-    instructionIterator.removeOrReplaceByDebugLocalRead();
-  }
 
   private void insertReadCast(
       IRCode code,
@@ -1088,23 +943,6 @@
     affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
   }
 
-  private Argument rewriteArgumentType(
-      IRCode code,
-      Argument argument,
-      RewrittenTypeInfo rewrittenTypeInfo,
-      Set<Phi> affectedPhis,
-      int newArgumentIndex) {
-    TypeElement rewrittenType = rewrittenTypeInfo.getNewType().toTypeElement(appView);
-    Argument replacement =
-        Argument.builder()
-            .setIndex(newArgumentIndex)
-            .setFreshOutValue(code, rewrittenType, argument.getLocalInfo())
-            .setPosition(argument.getPosition())
-            .build();
-    affectedPhis.addAll(argument.outValue().uniquePhiUsers());
-    return replacement;
-  }
-
   private void removeUnusedArguments(IRCode code, Set<UnusedArgument> unusedArguments) {
     AffectedValues affectedValues = new AffectedValues();
     for (UnusedArgument unusedArgument : unusedArguments) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
index 480f608..fc99554 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
@@ -6,10 +6,19 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.lightir.LirConstant;
@@ -17,28 +26,69 @@
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.google.common.collect.ConcurrentHashMultiset;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Multiset;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public class ThrowBlockOutline implements LirConstant {
 
-  @SuppressWarnings("UnusedVariable")
+  private final List<AbstractValue> arguments;
   private final LirCode<?> lirCode;
-
   private final DexProto proto;
   private final Multiset<DexMethod> users = ConcurrentHashMultiset.create();
 
   private ProgramMethod materializedOutlineMethod;
 
   ThrowBlockOutline(LirCode<?> lirCode, DexProto proto) {
+    this.arguments = proto.getArity() == 0 ? Collections.emptyList() : new ArrayList<>();
     this.lirCode = lirCode;
     this.proto = proto;
   }
 
-  public void addUser(DexMethod user, List<Value> unusedArguments) {
+  public void addUser(
+      DexMethod user, List<Value> userArguments, AbstractValueFactory valueFactory) {
+    assert userArguments.size() == proto.getArity();
     users.add(user);
-    // TODO(TODO(b/434769547)): Compute abstraction of arguments to enable interprocedural constant
-    //  propagation.
+    if (!userArguments.isEmpty()) {
+      if (arguments.isEmpty()) {
+        for (Value userArgument : userArguments) {
+          arguments.add(encodeArgumentValue(userArgument, valueFactory));
+        }
+      } else {
+        for (int i = 0; i < userArguments.size(); i++) {
+          AbstractValue existingArgument = arguments.get(i);
+          if (existingArgument.isUnknown()) {
+            continue;
+          }
+          Value userArgument = userArguments.get(i);
+          if (!existingArgument.equals(encodeArgumentValue(userArgument, valueFactory))) {
+            arguments.set(i, AbstractValue.unknown());
+          }
+        }
+      }
+    }
+  }
+
+  private AbstractValue encodeArgumentValue(Value value, AbstractValueFactory valueFactory) {
+    if (value.isConstNumber()) {
+      ConstNumber constNumber = value.getDefinition().asConstNumber();
+      TypeElement type = value.getType();
+      if (type.isReferenceType()) {
+        return valueFactory.createNullValue(type);
+      } else {
+        return valueFactory.createSingleNumberValue(constNumber.getRawValue(), type);
+      }
+    } else if (value.isConstString()) {
+      ConstString constString = value.getDefinition().asConstString();
+      return valueFactory.createSingleStringValue(constString.getValue());
+    }
+    return AbstractValue.unknown();
+  }
+
+  public List<AbstractValue> getArguments() {
+    return arguments;
   }
 
   @Override
@@ -58,6 +108,27 @@
     return proto;
   }
 
+  public RewrittenPrototypeDescription getProtoChanges() {
+    assert hasConstantArgument();
+    ArgumentInfoCollection.Builder argumentsInfoBuilder =
+        ArgumentInfoCollection.builder().setArgumentInfosSize(proto.getArity());
+    for (int i = 0; i < arguments.size(); i++) {
+      if (isArgumentConstant(i)) {
+        RemovedArgumentInfo removedArgumentInfo =
+            RemovedArgumentInfo.builder()
+                .setSingleValue(arguments.get(i).asSingleValue())
+                .setType(proto.getParameter(i))
+                .build();
+        argumentsInfoBuilder.addArgumentInfo(i, removedArgumentInfo);
+      }
+    }
+    return RewrittenPrototypeDescription.createForArgumentsInfo(argumentsInfoBuilder.build());
+  }
+
+  public DexProto getOptimizedProto(DexItemFactory factory) {
+    return proto.withoutParameters((i, p) -> isArgumentConstant(i), factory);
+  }
+
   public ProgramMethod getSynthesizingContext(AppView<?> appView) {
     DexMethod shortestUser = null;
     for (DexMethod user : users) {
@@ -81,6 +152,10 @@
     return users;
   }
 
+  public boolean hasConstantArgument() {
+    return Iterables.any(arguments, argument -> !argument.isUnknown());
+  }
+
   @Override
   public int internalLirConstantAcceptCompareTo(LirConstant other, CompareToVisitor visitor) {
     throw new Unreachable();
@@ -91,6 +166,10 @@
     throw new Unreachable();
   }
 
+  public boolean isArgumentConstant(int index) {
+    return !arguments.get(index).isUnknown();
+  }
+
   public boolean isMaterialized() {
     return materializedOutlineMethod != null;
   }
@@ -106,6 +185,6 @@
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setCode(methodSig -> lirCode)
-                    .setProto(proto));
+                    .setProto(getOptimizedProto(appView.dexItemFactory())));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
index 00ba07a..d375841 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
@@ -13,16 +13,27 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.ThrowBlockOutlineMarker;
+import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRFinalizer;
+import com.android.tools.r8.ir.conversion.LensCodeArgumentRewriter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.passes.DexConstantOptimizer;
+import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.lightir.Lir2IRConverter;
 import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirStrategy;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.timing.Timing;
+import java.util.Collections;
+import java.util.Set;
 
 /** Rewriter that processes {@link ThrowBlockOutlineMarker} instructions. */
 public class ThrowBlockOutlineMarkerRewriter {
@@ -35,16 +46,20 @@
     this.deadCodeRemover = new DeadCodeRemover(appView);
   }
 
-  public void processMethod(ProgramMethod method) {
+  public void processMethod(ProgramMethod method, ThrowBlockOutline outline) {
     assert method.getDefinition().hasCode();
     assert method.getDefinition().getCode().isLirCode();
     // Build IR.
-    LirCode<?> lirCode = method.getDefinition().getCode().asLirCode();
-    IRCode code = lirCode.buildIR(method, appView);
+    LirCode<Integer> lirCode = method.getDefinition().getCode().asLirCode();
+    IRCode code = buildIR(lirCode, method, outline);
     assert code.getConversionOptions().isGeneratingDex();
 
     // Process IR.
-    processOutlineMarkers(code);
+    if (outline != null) {
+      removeConstantArgumentsFromOutline(method, code, outline);
+    } else {
+      processOutlineMarkers(code);
+    }
 
     // Convert to DEX.
     IRFinalizer<?> finalizer = code.getConversionOptions().getFinalizer(deadCodeRemover, appView);
@@ -52,7 +67,39 @@
     method.setCode(dexCode, appView);
   }
 
+  private IRCode buildIR(
+      LirCode<Integer> lirCode, ProgramMethod method, ThrowBlockOutline outline) {
+    if (outline == null || !outline.hasConstantArgument()) {
+      return lirCode.buildIR(method, appView);
+    }
+    // We need to inform IR construction to insert the arguments that have been removed from the
+    // proto.
+    Position callerPosition = null;
+    return Lir2IRConverter.translate(
+        method,
+        lirCode,
+        LirStrategy.getDefaultStrategy().getDecodingStrategy(lirCode, new NumberGenerator()),
+        appView,
+        callerPosition,
+        outline.getProtoChanges(),
+        MethodConversionOptions.forD8(appView, method));
+  }
+
+  private void removeConstantArgumentsFromOutline(
+      ProgramMethod method, IRCode code, ThrowBlockOutline outline) {
+    Set<Phi> affectedPhis = Collections.emptySet();
+    Set<UnusedArgument> unusedArguments = Collections.emptySet();
+    new LensCodeArgumentRewriter(appView)
+        .rewriteArguments(
+            code, method.getReference(), outline.getProtoChanges(), affectedPhis, unusedArguments);
+
+    // Run shorten live ranges to push materialized constants to their uses.
+    ConstantCanonicalizer constantCanonicalizer = new ConstantCanonicalizer(appView, method, code);
+    new DexConstantOptimizer(appView, constantCanonicalizer).run(code, Timing.empty());
+  }
+
   private void processOutlineMarkers(IRCode code) {
+    boolean needsDeadCodeElimination = false;
     for (BasicBlock block : code.getBlocks()) {
       Throw throwInstruction = block.exit().asThrow();
       if (throwInstruction != null) {
@@ -60,6 +107,11 @@
             block.entry().nextUntilInclusive(Instruction::isThrowBlockOutlineMarker);
         if (outlineMarker != null) {
           ThrowBlockOutline outline = outlineMarker.getOutline();
+          if (outlineMarker.detachConstantOutlineArguments(outline)) {
+            // Make sure to run dead code elimination when arguments are detached, since detached
+            // values may become dead.
+            needsDeadCodeElimination = true;
+          }
           if (outline.isMaterialized()) {
             // Insert a call to the materialized outline method and load the return value.
             BasicBlockInstructionListIterator instructionIterator =
@@ -96,6 +148,10 @@
       }
       assert block.streamInstructions().noneMatch(Instruction::isThrowBlockOutlineMarker);
     }
+
+    if (needsDeadCodeElimination) {
+      new DeadCodeRemover(appView).run(code, Timing.empty());
+    }
   }
 
   private Value addReturnValue(IRCode code, BasicBlockInstructionListIterator instructionIterator) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
index a131719..d10d0e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutliner.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -124,30 +123,31 @@
   private void processMethods(
       Collection<ThrowBlockOutline> outlines, ExecutorService executorService)
       throws ExecutionException {
-    ProgramMethodSet methodsToProcess = getMethodsToReprocess(outlines);
+    ProgramMethodMap<ThrowBlockOutline> methodsToReprocess = getMethodsToReprocess(outlines);
     ThrowBlockOutlineMarkerRewriter rewriter = new ThrowBlockOutlineMarkerRewriter(appView);
-    ThreadUtils.processItems(
-        methodsToProcess,
+    ThreadUtils.processMap(
+        methodsToReprocess,
         rewriter::processMethod,
         appView.options().getThreadingModule(),
         executorService);
   }
 
-  private ProgramMethodSet getMethodsToReprocess(Collection<ThrowBlockOutline> outlines) {
-    ProgramMethodSet methodsToProcess = ProgramMethodSet.create();
+  private ProgramMethodMap<ThrowBlockOutline> getMethodsToReprocess(
+      Collection<ThrowBlockOutline> outlines) {
+    ProgramMethodMap<ThrowBlockOutline> methodsToReprocess = ProgramMethodMap.create();
     Set<DexMethod> seenUsers = Sets.newIdentityHashSet();
     for (ThrowBlockOutline outline : outlines) {
       for (DexMethod user : outline.getUsers()) {
         if (seenUsers.add(user)) {
-          ProgramMethod methodToProcess = appView.definitionFor(user).asProgramMethod();
-          methodsToProcess.add(methodToProcess);
+          ProgramMethod methodToReprocess = appView.definitionFor(user).asProgramMethod();
+          methodsToReprocess.put(methodToReprocess, null);
         }
       }
       if (outline.getMaterializedOutlineMethod() != null) {
-        methodsToProcess.add(outline.getMaterializedOutlineMethod());
+        methodsToReprocess.put(outline.getMaterializedOutlineMethod(), outline);
       }
     }
-    return methodsToProcess;
+    return methodsToReprocess;
   }
 
   private boolean supplyOutlineConsumerForTesting(Collection<ThrowBlockOutline> outlines) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
index 980668a..1dab9c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexTypeUtils;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator;
@@ -57,6 +58,7 @@
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
+  private final AbstractValueFactory valueFactory = new AbstractValueFactory();
 
   private final Map<Wrapper<LirCode<?>>, ThrowBlockOutline> outlines = new ConcurrentHashMap<>();
 
@@ -69,6 +71,12 @@
     new ThrowBlockOutlinerScannerForCode(code).run();
   }
 
+  public AbstractValueFactory getAbstractValueFactory() {
+    // If/when extending this to R8, use the R8 AbstractValueFactory from AppView.
+    assert !appView.enableWholeProgramOptimizations();
+    return valueFactory;
+  }
+
   public Collection<ThrowBlockOutline> getOutlines() {
     return outlines.values();
   }
@@ -126,7 +134,7 @@
             // TODO(b/434769547): This may not hold. We should compute the join.
             assert proto.isIdenticalTo(outline.getProto());
             List<Value> arguments = outlineBuilder.buildArguments();
-            outline.addUser(code.reference(), arguments);
+            outline.addUser(code.reference(), arguments, getAbstractValueFactory());
 
             // Insert a synthetic marker instruction that references the outline so that we know
             // where to materialize the outline call.
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index d0bd1a9..8dea431 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -125,12 +125,16 @@
    * @param emptyArray an empty array
    * @return an array with written elements
    */
-  @SuppressWarnings("unchecked")
   public static <S, T> T[] map(S[] original, Function<S, T> mapper, T[] emptyArray) {
+    return map(original, (i, s) -> mapper.apply(s), emptyArray);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <S, T> T[] map(S[] original, IntObjToObjFunction<S, T> mapper, T[] emptyArray) {
     ArrayList<T> results = null;
     for (int i = 0; i < original.length; i++) {
       S oldOne = original[i];
-      T newOne = mapper.apply(oldOne);
+      T newOne = mapper.apply(i, oldOne);
       if (newOne == oldOne) {
         if (results != null) {
           results.add((T) oldOne);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
index 2e130f7..91908b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerSharedStringBuilderTest.java
@@ -15,8 +15,10 @@
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -74,7 +76,12 @@
     assertEquals(1, outlines.size());
     ThrowBlockOutline outline = outlines.iterator().next();
     assertEquals(2, outline.getNumberOfUsers());
-    assertEquals(4, outline.getProto().getArity());
+    assertEquals(5, outline.getProto().getArity());
+
+    // Verify that the last argument is known to be constant.
+    AbstractValue lastArgument = ListUtils.last(outline.getArguments());
+    assertTrue(lastArgument.isSingleStringValue());
+    assertEquals(", k=42", lastArgument.asSingleStringValue().getDexString().toString());
   }
 
   private void inspectOutput(CodeInspector inspector) {
@@ -85,12 +92,14 @@
     assertThat(outlineClassSubject, isPresent());
     assertEquals(1, outlineClassSubject.allMethods().size());
 
-    // Validate that the outline uses StringBuilder.
+    // Validate that the outline uses StringBuilder and that the string ", k=42" has been moved into
+    // the outline.
     MethodSubject outlineMethodSubject = outlineClassSubject.uniqueMethod();
     assertTrue(
         outlineMethodSubject
             .streamInstructions()
             .anyMatch(i -> i.isNewInstance("java.lang.StringBuilder")));
+    assertTrue(outlineMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", k=42")));
 
     // Validate that main() no longer uses StringBuilder and that it calls the outline twice.
     MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
@@ -103,6 +112,7 @@
     assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString("j=")));
     assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", j=")));
     assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(", i=")));
+    assertTrue(mainMethodSubject.streamInstructions().noneMatch(i -> i.isConstString(", k=42")));
     assertEquals(
         2,
         mainMethodSubject
@@ -117,10 +127,10 @@
       int i = Integer.parseInt(args[0]);
       int j = Integer.parseInt(args[1]);
       if (i == 0) {
-        throw new IllegalArgumentException("i=" + i + ", j=" + j);
+        throw new IllegalArgumentException("i=" + i + ", j=" + j + ", k=42");
       }
       if (j == 0) {
-        throw new IllegalArgumentException("j=" + j + ", i=" + i);
+        throw new IllegalArgumentException("j=" + j + ", i=" + i + ", k=42");
       }
     }
   }