Extend bridge analyzer to bridges that target static methods
This extends the bridge analyzer to cover the main method of synthetic
lambda classes, which typically target static javac synthetic lambda$
methods (except when method handles are used).
Bug: b/309575527
Change-Id: Ic87992568c97b7a0e65eb4cbc512a6117d824ef2
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
index 780eff6..98b8c4b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.internal.BooleanUtils;
@@ -36,11 +35,15 @@
/** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
public static BridgeInfo analyzeMethod(AppView<?> appView, DexEncodedMethod method, IRCode code) {
DexItemFactory factory = appView.dexItemFactory();
+ // The main method of lambda classes forward all arguments except the receiver (i.e., the lambda
+ // instance) to the synthetic javac lambda$ method using an invoke-static. If the receiver is
+ // unused, we require that argument i is passed as argument i-1 to the invoke-static for i>0.
+ int forwardArgumentOffset = method.isInstance() && code.getThis().isUnused() ? -1 : 0;
// Scan through the instructions one-by-one. We expect a sequence of Argument instructions,
// followed by a (possibly empty) sequence of CheckCast/Box/Unbox operations followed by a
// single InvokeMethod instruction, followed by an optional CheckCast/Box/Unbox instruction,
// followed by a Return instruction.
- InvokeMethodWithReceiver uniqueInvoke = null;
+ InvokeMethod uniqueInvoke = null;
// The unique cast, box, or unbox operation prior to the return instruction.
Instruction uniqueReturnCheckCastBoxOrUnbox = null;
InstructionListIterator instructionIterator = code.entryBlock().listIterator();
@@ -54,7 +57,8 @@
case CHECK_CAST:
{
- if (!analyzeCheckCastBoxOrUnbox(method, instruction, uniqueInvoke)) {
+ if (!analyzeCheckCastBoxOrUnbox(
+ method, instruction, uniqueInvoke, forwardArgumentOffset)) {
return failure();
}
// If we have moved past the single invoke instruction, then record that this cast is
@@ -73,7 +77,8 @@
{
InvokeMethod invoke = instruction.asInvokeMethod();
if (isBoxingInvoke(invoke, factory) || isUnboxingInvoke(invoke, factory)) {
- if (!analyzeCheckCastBoxOrUnbox(method, instruction, uniqueInvoke)) {
+ if (!analyzeCheckCastBoxOrUnbox(
+ method, instruction, uniqueInvoke, forwardArgumentOffset)) {
return failure();
}
// If we have moved past the single invoke instruction, then record that this boxing
@@ -85,8 +90,6 @@
uniqueReturnCheckCastBoxOrUnbox = invoke;
}
break;
- } else if (invoke.isInvokeStatic()) {
- return failure();
} else {
// Intentionally fall through.
}
@@ -99,8 +102,8 @@
if (uniqueInvoke != null) {
return failure();
}
- InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
- if (!analyzeInvoke(invoke, factory)) {
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ if (!analyzeInvoke(invoke, factory, forwardArgumentOffset)) {
return failure();
}
// Record that we have seen the single invoke instruction.
@@ -140,14 +143,29 @@
assert uniqueInvoke != null;
assert uniqueInvoke.isInvokeDirect()
+ || uniqueInvoke.isInvokeStatic()
|| uniqueInvoke.isInvokeSuper()
|| uniqueInvoke.isInvokeVirtual();
switch (uniqueInvoke.getType()) {
case DIRECT:
+ if (forwardArgumentOffset != 0) {
+ return null;
+ }
return new DirectBridgeInfo(uniqueInvoke.getInvokedMethod());
+ case STATIC:
+ return forwardArgumentOffset == 0
+ ? new StaticBridgeInfo(uniqueInvoke.getInvokedMethod())
+ : new StaticBridgeExcludingReceiverInfo(
+ uniqueInvoke.getInvokedMethod(), uniqueInvoke.getInterfaceBit());
case SUPER:
+ if (forwardArgumentOffset != 0) {
+ return null;
+ }
return new SuperBridgeInfo(uniqueInvoke.getInvokedMethod());
case VIRTUAL:
+ if (forwardArgumentOffset != 0) {
+ return null;
+ }
return new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
default:
throw new Unreachable();
@@ -155,14 +173,18 @@
}
private static boolean analyzeCheckCastBoxOrUnbox(
- DexEncodedMethod method, Instruction checkCastBoxOrUnbox, InvokeMethod invoke) {
+ DexEncodedMethod method,
+ Instruction checkCastBoxOrUnbox,
+ InvokeMethod invoke,
+ int forwardArgumentOffset) {
return invoke == null
- ? analyzeCheckCastBoxOrUnboxBeforeInvoke(checkCastBoxOrUnbox)
+ ? analyzeCheckCastBoxOrUnboxBeforeInvoke(checkCastBoxOrUnbox, forwardArgumentOffset)
: analyzeCheckCastBoxOrUnboxAfterInvoke(method, checkCastBoxOrUnbox, invoke);
}
@SuppressWarnings("ReferenceEquality")
- private static boolean analyzeCheckCastBoxOrUnboxBeforeInvoke(Instruction checkCastBoxOrUnbox) {
+ private static boolean analyzeCheckCastBoxOrUnboxBeforeInvoke(
+ Instruction checkCastBoxOrUnbox, int forwardArgumentOffset) {
Value object = checkCastBoxOrUnbox.getFirstOperand().getAliasedValue();
// It must be processing one of the arguments.
if (!object.isArgument()) {
@@ -185,28 +207,28 @@
if (invoke == null) {
return false;
}
- if (invoke.arguments().size() <= argumentIndex) {
+ // The cast value must be used in the same argument position, unless the `this` value is unused.
+ int expectedForwardArgumentIndex = argumentIndex + forwardArgumentOffset;
+ if (invoke.arguments().size() <= expectedForwardArgumentIndex
+ || invoke.getArgument(expectedForwardArgumentIndex) != outValue) {
return false;
}
- // The cast value must be used in the same argument position.
- if (invoke.getArgument(argumentIndex) != outValue) {
- return false;
- }
- int parameterIndex = argumentIndex - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
+ int forwardParameterIndex =
+ expectedForwardArgumentIndex - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
// It is not allowed to cast the receiver.
- if (parameterIndex == -1) {
+ if (forwardParameterIndex == -1) {
return false;
}
// The type of the cast must match the type of the parameter.
if (checkCastBoxOrUnbox.isCheckCast()) {
if (checkCastBoxOrUnbox.asCheckCast().getType()
- != invoke.getInvokedMethod().getParameter(parameterIndex)) {
+ != invoke.getInvokedMethod().getParameter(forwardParameterIndex)) {
return false;
}
} else {
InvokeMethod boxOrUnbox = checkCastBoxOrUnbox.asInvokeMethod();
if (boxOrUnbox.getInvokedMethod().getReturnType()
- != invoke.getInvokedMethod().getParameter(parameterIndex)) {
+ != invoke.getInvokedMethod().getParameter(forwardParameterIndex)) {
return false;
}
}
@@ -241,17 +263,20 @@
&& castValue.singleUniqueUser().isReturn();
}
- private static boolean analyzeInvoke(InvokeMethodWithReceiver invoke, DexItemFactory factory) {
+ private static boolean analyzeInvoke(
+ InvokeMethod invoke, DexItemFactory factory, int forwardArgumentOffset) {
// All of the forwarded arguments of the enclosing method must be in the same argument position.
for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
Value argument = invoke.getArgument(argumentIndex).getAliasedValue();
if (argument.isPhi()) {
return false;
- } else if (argument.isArgument()
- && argumentIndex != argument.getDefinition().asArgument().getIndex()) {
- return false;
+ } else if (argument.isArgument()) {
+ int incomingArgumentIndex = argument.getDefinition().asArgument().getIndex();
+ if (argumentIndex != incomingArgumentIndex + forwardArgumentOffset) {
+ return false;
+ }
} else if (isCheckCastBoxOrUnbox(argument.getDefinition(), factory)) {
- int expectedArgumentIndex =
+ int incomingArgumentIndex =
argument
.getDefinition()
.getFirstOperand()
@@ -259,7 +284,7 @@
.getDefinition()
.asArgument()
.getIndex();
- if (argumentIndex != expectedArgumentIndex) {
+ if (argumentIndex != incomingArgumentIndex + forwardArgumentOffset) {
return false;
}
}
@@ -272,11 +297,12 @@
.getAliasedValue(new BridgeAnalyzerAliasedValueConfiguration(factory))
.isArgument()
&& invoke
- .getArgument(argumentIndex)
- .getAliasedValue(new BridgeAnalyzerAliasedValueConfiguration(factory))
- .getDefinition()
- .asArgument()
- .getIndex()
+ .getArgument(argumentIndex)
+ .getAliasedValue(new BridgeAnalyzerAliasedValueConfiguration(factory))
+ .getDefinition()
+ .asArgument()
+ .getIndex()
+ + forwardArgumentOffset
== argumentIndex);
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index fdb5092..7489c43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -3,12 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.info.bridge;
+import com.android.tools.r8.graph.DexMethod;
+
/**
* A piece of optimization info that is computed for bridge methods. The info stores details about
* the behavior of the bridge.
*/
public abstract class BridgeInfo {
+ // The targeted method.
+ private final DexMethod invokedMethod;
+
+ public BridgeInfo(DexMethod invokedMethod) {
+ this.invokedMethod = invokedMethod;
+ }
+
public boolean isDirectBridgeInfo() {
return false;
}
@@ -17,6 +26,26 @@
return null;
}
+ public boolean isStaticBridgeInfo() {
+ return false;
+ }
+
+ public StaticBridgeInfo asStaticBridgeInfo() {
+ return null;
+ }
+
+ public boolean isStaticBridgeExcludingReceiverInfo() {
+ return false;
+ }
+
+ public StaticBridgeExcludingReceiverInfo asStaticBridgeExcludingReceiverInfo() {
+ return null;
+ }
+
+ public DexMethod getInvokedMethod() {
+ return invokedMethod;
+ }
+
public boolean isSuperBridgeInfo() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java
index 3579edf..b704d35 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/DirectBridgeInfo.java
@@ -26,15 +26,8 @@
*/
public class DirectBridgeInfo extends BridgeInfo {
- // The targeted method.
- private final DexMethod invokedMethod;
-
public DirectBridgeInfo(DexMethod invokedMethod) {
- this.invokedMethod = invokedMethod;
- }
-
- public DexMethod getInvokedMethod() {
- return invokedMethod;
+ super(invokedMethod);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeExcludingReceiverInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeExcludingReceiverInfo.java
new file mode 100644
index 0000000..af71dc8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeExcludingReceiverInfo.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2026, 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.optimize.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/**
+ * Optimization info computed for non-static bridge methods that use an invoke-static instruction,
+ * where the `this` value is not forwarded.
+ *
+ * <p>This typically occurs in D8/R8 synthesized lambda classes that call javac synthetic lambda
+ * methods.
+ */
+public class StaticBridgeExcludingReceiverInfo extends BridgeInfo {
+
+ private final boolean isInterface;
+
+ public StaticBridgeExcludingReceiverInfo(DexMethod invokedMethod, boolean isInterface) {
+ super(invokedMethod);
+ this.isInterface = isInterface;
+ }
+
+ public boolean getInterfaceBit() {
+ return isInterface;
+ }
+
+ @Override
+ public boolean isStaticBridgeExcludingReceiverInfo() {
+ return true;
+ }
+
+ @Override
+ public StaticBridgeExcludingReceiverInfo asStaticBridgeExcludingReceiverInfo() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeInfo.java
new file mode 100644
index 0000000..58c2c90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/StaticBridgeInfo.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2026, 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.optimize.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/** Optimization info computed for bridge methods that use an invoke-static instruction. */
+public class StaticBridgeInfo extends BridgeInfo {
+
+ public StaticBridgeInfo(DexMethod invokedMethod) {
+ super(invokedMethod);
+ }
+
+ @Override
+ public boolean isStaticBridgeInfo() {
+ return true;
+ }
+
+ @Override
+ public StaticBridgeInfo asStaticBridgeInfo() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/SuperBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/SuperBridgeInfo.java
index 54b74f4..ad30beb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/SuperBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/SuperBridgeInfo.java
@@ -26,15 +26,8 @@
*/
public class SuperBridgeInfo extends BridgeInfo {
- // The targeted method.
- private final DexMethod invokedMethod;
-
public SuperBridgeInfo(DexMethod invokedMethod) {
- this.invokedMethod = invokedMethod;
- }
-
- public DexMethod getInvokedMethod() {
- return invokedMethod;
+ super(invokedMethod);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
index a07abdc..0697ce6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -26,15 +26,8 @@
*/
public class VirtualBridgeInfo extends BridgeInfo {
- // The targeted method.
- private final DexMethod invokedMethod;
-
public VirtualBridgeInfo(DexMethod invokedMethod) {
- this.invokedMethod = invokedMethod;
- }
-
- public DexMethod getInvokedMethod() {
- return invokedMethod;
+ super(invokedMethod);
}
@Override