Skip to content

Commit 486370a

Browse files
committed
[GR-65296] Use implemented interface as differentiator when generating stable lambda name.
PullRequest: graal/20907
2 parents 2c1cc0e + 6ae8b0f commit 486370a

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/test/LambdaStableNameTest.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
package jdk.graal.compiler.hotspot.test;
2727

2828
import static org.junit.Assert.assertEquals;
29+
import static org.junit.Assert.assertNotEquals;
2930
import static org.junit.Assert.assertNotNull;
3031
import static org.junit.Assert.assertTrue;
3132
import static org.junit.Assert.fail;
3233

3334
import java.math.BigInteger;
35+
import java.util.function.Function;
3436

3537
import org.junit.Test;
3638
import org.objectweb.asm.Type;
@@ -40,22 +42,43 @@
4042
import jdk.vm.ci.runtime.JVMCI;
4143

4244
public class LambdaStableNameTest {
45+
4346
@Test
4447
public void checkStableLamdaNameForRunnableAndAutoCloseable() {
4548
String s = "a string";
46-
Runnable r = s::hashCode;
47-
ResolvedJavaType rType = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaType(r.getClass());
49+
Runnable r0 = s::hashCode;
50+
String r0Name = getLambdaName(r0.getClass());
4851

49-
String name = LambdaUtils.findStableLambdaName(rType);
50-
assertLambdaName(name);
52+
Runnable r1 = s::hashCode;
53+
String r1Name = getLambdaName(r1.getClass());
54+
55+
assertEquals("The two stable lambda names should the same as they reference the same method and implement the same interface", r0Name, r1Name);
5156

5257
AutoCloseable ac = s::hashCode;
53-
ResolvedJavaType acType = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaType(ac.getClass());
54-
String acName = LambdaUtils.findStableLambdaName(acType);
55-
assertEquals("Both stable lambda names are the same as they reference the same method", name, acName);
58+
String acName = getLambdaName(ac.getClass());
59+
60+
assertNotEquals("The two stable lambda names should not be the same as they reference the same method but implement different interfaces", r0Name, acName);
5661

5762
String myName = Type.getInternalName(getClass());
58-
assertEquals("The name known in 24.0 version is computed", "L" + myName + "$$Lambda.0x605511206480068bfd9e0bafd4f79e22;", name);
63+
assertEquals("The name known in 24.0 version is computed", "L" + myName + "$$Lambda.0x59cf38d78b5471f8ea57f1c28b37039c;", r0Name);
64+
65+
Function<String, Integer> f0 = (str) -> str.hashCode();
66+
String f0Name = getLambdaName(f0.getClass());
67+
68+
interface ValueTransformer<L, R> extends Function<L, R> {
69+
}
70+
71+
ValueTransformer<String, Integer> f1 = (str) -> str.hashCode();
72+
String f1Name = getLambdaName(f1.getClass());
73+
74+
assertNotEquals("The two stable lambda names should not be the same as they reference the same method but implement different interfaces", f0Name, f1Name);
75+
}
76+
77+
private static String getLambdaName(Class<?> clazz) {
78+
ResolvedJavaType type = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaType(clazz);
79+
String name = LambdaUtils.findStableLambdaName(type);
80+
assertLambdaName(name);
81+
return name;
5982
}
6083

6184
private static void assertLambdaName(String name) {

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.regex.Pattern;
3838

3939
import jdk.graal.compiler.bytecode.BytecodeStream;
40+
import jdk.graal.compiler.debug.GraalError;
4041
import jdk.graal.compiler.util.Digest;
4142
import jdk.vm.ci.common.JVMCIError;
4243
import jdk.vm.ci.meta.ConstantPool;
@@ -141,18 +142,28 @@ public static boolean isLambdaName(String name) {
141142
return isLambdaClassName(name) && lambdaMatcher(name).find();
142143
}
143144

144-
private static String createStableLambdaName(ResolvedJavaType lambdaType, List<JavaMethod> targetMethods) {
145+
private static String createStableLambdaName(ResolvedJavaType lambdaType, List<JavaMethod> invokedMethods) {
145146
final String lambdaName = lambdaType.getName();
146147
assert lambdaMatcher(lambdaName).find() : "Stable name should be created for lambda types: " + lambdaName;
147148

148149
Matcher m = lambdaMatcher(lambdaName);
150+
/* Generate lambda signature by hashing its composing parts. */
149151
StringBuilder sb = new StringBuilder();
150-
targetMethods.forEach((targetMethod) -> sb.append(targetMethod.format("%H.%n(%P)%R")));
151-
// Take parameter types of constructor into consideration, see GR-52837
152+
/* Append invoked methods. */
153+
for (JavaMethod method : invokedMethods) {
154+
sb.append(method.format("%H.%n(%P)%R"));
155+
}
156+
/* Append constructor parameter types. */
152157
for (JavaMethod ctor : lambdaType.getDeclaredConstructors()) {
153158
sb.append(ctor.format("%P"));
154159
}
155-
return m.replaceFirst(Matcher.quoteReplacement(LAMBDA_CLASS_NAME_SUBSTRING + ADDRESS_PREFIX + Digest.digestAsHex(sb.toString()) + ";"));
160+
/* Append implemented interfaces. */
161+
for (ResolvedJavaType iface : lambdaType.getInterfaces()) {
162+
sb.append(iface.toJavaName());
163+
}
164+
String signature = Digest.digestAsHex(sb.toString());
165+
GraalError.guarantee(signature.length() == 32, "Expecting a 32 digits long hex value.");
166+
return m.replaceFirst(Matcher.quoteReplacement(LAMBDA_CLASS_NAME_SUBSTRING + ADDRESS_PREFIX + signature + ";"));
156167
}
157168

158169
private static Matcher lambdaMatcher(String value) {

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/Digest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,18 @@ private static void encodeBase62(long value, byte[] result, int resultIndex) {
123123
public static String digestAsHex(String value) {
124124
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
125125
LongLong hash = MurmurHash3_x64_128(bytes, 0, bytes.length, HASH_SEED);
126-
return Long.toHexString(hash.l1) + Long.toHexString(hash.l2);
126+
String asHex = toFixedSizeHexString(hash.l1) + toFixedSizeHexString(hash.l2);
127+
GraalError.guarantee(asHex.length() == 32, "Expecting a 32 digits long hex value.");
128+
return asHex;
129+
}
130+
131+
/**
132+
* Note: The string generated by {@link Long#toHexString(long)} can vary in length, and it
133+
* doesn't include any leading 0s. With {@link String#format(String, Object...)} we inject
134+
* leading 0s when necessary to ensure that the produced digest has a fixed size.
135+
*/
136+
private static String toFixedSizeHexString(long l) {
137+
return String.format("%016x", l);
127138
}
128139

129140
public static UUID digestAsUUID(String value) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaProxyRenamingSubstitutionProcessor.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,16 @@ private LambdaSubstitutionType getSubstitution(ResolvedJavaType original) {
7171
}
7272

7373
/**
74-
* Finds a unique name for a lambda proxies with a same target originating from the same class.
75-
*
76-
* NOTE: the name truly stable only in a single threaded build.
74+
* Finds a unique name for lambda proxies with the same target originating from the same class.
75+
* Method {@link LambdaUtils#findStableLambdaName(ResolvedJavaType)} hashes over a lambda type
76+
* descriptor (invoked methods, constructor parameters, implemented interfaces) in an effort to
77+
* find enough differences that can yield a unique name. However, this can still lead to
78+
* conflicts. To create unique names when conflicts are found the naming scheme uses a counter:
79+
* first a 0 is added to the end of the generated lambda name, and it is incremented until a
80+
* unique name is found. This also means that the name is truly stable only in a single threaded
81+
* build, i.e., when conflicts are discovered in the same order. Since the conflict counter
82+
* doesn't have an upper limit the generated lambda name length can vary but the base hash is
83+
* always 32 hex digits long.
7784
*/
7885
private String findUniqueLambdaProxyName(String lambdaTargetName) {
7986
synchronized (uniqueLambdaProxyNames) {
@@ -91,7 +98,22 @@ private String findUniqueLambdaProxyName(String lambdaTargetName) {
9198
}
9299
}
93100

101+
/**
102+
* Find the base name generated by {@link LambdaUtils#findStableLambdaName(ResolvedJavaType)} by
103+
* subtracting the counter injected by {@link #findUniqueLambdaProxyName(String)}. Note that the
104+
* counter can be on multiple digits but the base hash is always 32 hex digits long.
105+
*/
106+
private static String getBaseName(String lambdaTargetName) {
107+
int startIndexOfHash = lambdaTargetName.indexOf(LambdaUtils.ADDRESS_PREFIX) + LambdaUtils.ADDRESS_PREFIX.length();
108+
return lambdaTargetName.substring(0, startIndexOfHash + 32);
109+
}
110+
111+
/**
112+
* Check if this lambda's name conflicted with any other entries, i.e., if the conflict counter
113+
* ever got to 1.
114+
*/
94115
public boolean isNameAlwaysStable(String lambdaTargetName) {
95-
return !uniqueLambdaProxyNames.contains(lambdaTargetName.substring(0, lambdaTargetName.length() - 1) + "1;");
116+
String baseName = getBaseName(lambdaTargetName);
117+
return !uniqueLambdaProxyNames.contains(baseName + "1;");
96118
}
97119
}

0 commit comments

Comments
 (0)