Skip to content

Commit de22927

Browse files
committed
[GR-69684] Fix overloaded method caching regression in HostExecuteNode.
PullRequest: graal/22143
2 parents 87e570b + b6f9800 commit de22927

File tree

2 files changed

+142
-18
lines changed

2 files changed

+142
-18
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.graal.compiler.truffle.test;
26+
27+
import java.lang.invoke.MethodHandle;
28+
import java.util.List;
29+
30+
import org.graalvm.polyglot.Context;
31+
import org.graalvm.polyglot.HostAccess;
32+
import org.junit.Assert;
33+
import org.junit.Before;
34+
import org.junit.Test;
35+
import org.junit.runner.RunWith;
36+
import org.junit.runners.Parameterized;
37+
import org.junit.runners.Parameterized.Parameter;
38+
import org.junit.runners.Parameterized.Parameters;
39+
40+
import com.oracle.truffle.api.CompilerDirectives;
41+
import com.oracle.truffle.api.frame.VirtualFrame;
42+
import com.oracle.truffle.api.interop.ArityException;
43+
import com.oracle.truffle.api.interop.InteropLibrary;
44+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
45+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
46+
import com.oracle.truffle.api.interop.UnsupportedTypeException;
47+
import com.oracle.truffle.api.nodes.RootNode;
48+
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
49+
50+
import jdk.graal.compiler.nodes.NodeView;
51+
import jdk.graal.compiler.nodes.StructuredGraph;
52+
import jdk.graal.compiler.nodes.java.MethodCallTargetNode;
53+
54+
/**
55+
* Regression test for host interop call partial evaluation (HostExecuteNode).
56+
*/
57+
@RunWith(Parameterized.class)
58+
public class GR69684Test extends PartialEvaluationTest {
59+
60+
@Parameter(0) public String methodName;
61+
62+
@Parameters(name = "{0}")
63+
public static List<String> data() {
64+
return List.of("directMethod", "overloadedMethod");
65+
}
66+
67+
@Before
68+
public void setup() {
69+
setupContext(Context.newBuilder().allowExperimentalOptions(true).option("engine.CompilationFailureAction", "Throw").option("engine.BackgroundCompilation", "false").option(
70+
"compiler.TreatPerformanceWarningsAsErrors", "all").build());
71+
getContext().initialize(ProxyLanguage.ID);
72+
}
73+
74+
@HostAccess.Export
75+
public Object directMethod(@SuppressWarnings("unused") String a, final Object b) {
76+
return b;
77+
}
78+
79+
@HostAccess.Export
80+
public Object overloadedMethod(@SuppressWarnings("unused") String a, final Object b) {
81+
return b;
82+
}
83+
84+
@HostAccess.Export
85+
public Object overloadedMethod(@SuppressWarnings("unused") Object a, final Object b) {
86+
// not supposed to be called
87+
throw new AssertionError();
88+
}
89+
90+
@Test
91+
public void test() {
92+
var rootNode = new RootNode(ProxyLanguage.get(null)) {
93+
@Child InteropLibrary interop = InteropLibrary.getFactory().createDispatched(1);
94+
95+
@Override
96+
public Object execute(VirtualFrame frame) {
97+
try {
98+
Object receiver = ProxyLanguage.LanguageContext.get(this).getEnv().asGuestValue(GR69684Test.this);
99+
return interop.invokeMember(receiver, methodName, "a", "b");
100+
} catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) {
101+
throw CompilerDirectives.shouldNotReachHere(e);
102+
}
103+
}
104+
};
105+
106+
StructuredGraph graph = partialEval(rootNode);
107+
108+
// After PE, only a single invoke taking a constant MethodHandle is expected.
109+
var invokedMethods = graph.getNodes(MethodCallTargetNode.TYPE).stream().filter(methodCall -> {
110+
return !methodCall.arguments().stream().anyMatch(argument -> argument.isConstant() &&
111+
getMetaAccess().lookupJavaType(MethodHandle.class).isAssignableFrom(
112+
argument.stamp(NodeView.DEFAULT).javaType(getMetaAccess())));
113+
}).map(MethodCallTargetNode::targetMethod).toList();
114+
Assert.assertEquals("Unexpected invokes: " + invokedMethods, 0, invokedMethods.size());
115+
}
116+
}

truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostExecuteNode.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -152,14 +152,14 @@ static Object doVarArgs(Node node, SingleMethod method, Object obj, Object[] arg
152152
@Exclusive @Cached InlinedExactClassProfile receiverProfile,
153153
@Shared("errorBranch") @Cached InlinedBranchProfile errorBranch,
154154
@Shared("seenScope") @Cached InlinedBranchProfile seenDynamicScope,
155-
@Shared @Cached InlinedBranchProfile seenVargArgs,
155+
@Shared @Cached InlinedBranchProfile seenVarArgs,
156156
@Exclusive @Cached HostTargetMappingNode varArgsMappingNode,
157157
@Exclusive @CachedLibrary(limit = "3") InteropLibrary varArgsMappingInterop,
158158
@Cached(value = "hostContext.getGuestToHostCache()", allowUncached = true) GuestToHostCodeCache cache) throws ArityException, UnsupportedTypeException {
159159
boolean asVarArgs;
160160
int parameterCount = cachedMethod.getParameterCount();
161161
if (args.length == parameterCount) {
162-
seenVargArgs.enter(node);
162+
seenVarArgs.enter(node);
163163
Class<?> varArgParamType = cachedMethod.getParameterTypes()[parameterCount - 1];
164164
asVarArgs = !HostToTypeNode.canConvert(node, args[parameterCount - 1], varArgParamType,
165165
cachedMethod.getGenericParameterTypes()[parameterCount - 1],
@@ -256,33 +256,37 @@ static Object doSingleUncached(Node node, SingleMethod method, Object obj, Objec
256256
}
257257

258258
// Note: checkArgTypes must be evaluated after selectOverload.
259-
@SuppressWarnings({"unused", "static-method", "truffle-static-method"})
260259
@ExplodeLoop
261-
@Specialization(guards = {"method == cachedMethod", "checkArgTypes(args, cachedArgTypes, interop, hostContext)"}, limit = "LIMIT")
260+
@Specialization(guards = {"method == cachedMethod", "checkArgTypes(args, cachedArgTypes, interop, hostContext, overload)"}, limit = "LIMIT")
262261
static final Object doOverloadedCached(Node node, OverloadedMethod method, Object obj, Object[] args, HostContext hostContext,
263-
@Cached("method") OverloadedMethod cachedMethod,
262+
@Cached("method") @SuppressWarnings("unused") OverloadedMethod cachedMethod,
264263
@Exclusive @Cached HostToTypeNode toJavaNode,
265264
@Exclusive @Cached ToGuestValueNode toGuest,
266-
@CachedLibrary(limit = "LIMIT") InteropLibrary interop,
265+
@CachedLibrary(limit = "LIMIT") @SuppressWarnings("unused") InteropLibrary interop,
267266
@Cached("createArgTypesArray(args)") TypeCheckNode[] cachedArgTypes,
268267
@Cached("selectOverload(node, method, args, hostContext, cachedArgTypes)") SingleMethod overload,
269268
@Exclusive @Cached InlinedExactClassProfile receiverProfile,
270269
@Shared("errorBranch") @Cached InlinedBranchProfile errorBranch,
271270
@Shared("seenScope") @Cached InlinedBranchProfile seenVariableScope,
272-
@Shared @Cached InlinedBranchProfile seenVargArgs,
271+
@Shared @Cached InlinedBranchProfile seenVarArgs,
273272
@Exclusive @Cached HostTargetMappingNode varArgsMappingNode,
274273
@Exclusive @CachedLibrary(limit = "3") InteropLibrary varArgsMappingInterop,
275274
@Cached(value = "hostContext.getGuestToHostCache()", allowUncached = true) GuestToHostCodeCache cache) throws ArityException, UnsupportedTypeException {
275+
assert args.length == cachedArgTypes.length;
276276

277277
boolean asVarArgs;
278-
int parameterCount = cachedArgTypes.length;
278+
int parameterCount = overload.getParameterCount();
279279
if (overload.isVarArgs()) {
280-
seenVargArgs.enter(node);
281-
Class<?> varArgParamType = overload.getParameterTypes()[parameterCount - 1];
282-
asVarArgs = !HostToTypeNode.canConvert(node, args[parameterCount - 1], varArgParamType,
283-
overload.getGenericParameterTypes()[parameterCount - 1],
284-
null, hostContext, HostToTypeNode.COERCE,
285-
varArgsMappingInterop, varArgsMappingNode);
280+
seenVarArgs.enter(node);
281+
if (cachedArgTypes.length == parameterCount) {
282+
Class<?> varArgParamType = overload.getParameterTypes()[parameterCount - 1];
283+
asVarArgs = !HostToTypeNode.canConvert(node, args[parameterCount - 1], varArgParamType,
284+
overload.getGenericParameterTypes()[parameterCount - 1],
285+
null, hostContext, HostToTypeNode.COERCE,
286+
varArgsMappingInterop, varArgsMappingNode);
287+
} else {
288+
asVarArgs = true;
289+
}
286290
} else {
287291
asVarArgs = false;
288292
}
@@ -323,7 +327,6 @@ static final Object doOverloadedCached(Node node, OverloadedMethod method, Objec
323327
}
324328
}
325329

326-
@SuppressWarnings("static-method")
327330
@Specialization(replaces = "doOverloadedCached")
328331
static final Object doOverloadedUncached(Node node, OverloadedMethod method, Object obj, Object[] args, HostContext hostContext,
329332
@Shared("toHost") @Cached HostToTypeNode toJavaNode,
@@ -446,7 +449,7 @@ private static void fillArgTypesArray(Node node, Object[] args, TypeCheckNode[]
446449
cachedArgTypes[i] = node.insert(argType);
447450
}
448451

449-
assert checkArgTypes(args, cachedArgTypes, InteropLibrary.getFactory().getUncached(), context) : Arrays.toString(cachedArgTypes);
452+
assert checkArgTypes(args, cachedArgTypes, InteropLibrary.getFactory().getUncached(), context, selected) : Arrays.toString(cachedArgTypes);
450453
}
451454

452455
private static TypeCheckNode createPrimitiveTargetCheck(List<SingleMethod> applicable, SingleMethod selected, Object arg, Class<?> targetType, int parameterIndex, int priority, boolean varArgs,
@@ -482,8 +485,13 @@ private static TypeCheckNode createPrimitiveTargetCheck(List<SingleMethod> appli
482485
return new PrimitiveType(currentTargetType, otherPossibleTypes.toArray(EMPTY_CLASS_ARRAY), priority);
483486
}
484487

488+
/**
489+
* @param overload dummy argument used to ensure this guard is not evaluated until
490+
* {@link #fillArgTypesArray} has been called by {@link #selectOverload}.
491+
* @see #doOverloadedCached
492+
*/
485493
@ExplodeLoop
486-
static boolean checkArgTypes(Object[] args, TypeCheckNode[] argTypes, InteropLibrary interop, HostContext context) {
494+
static boolean checkArgTypes(Object[] args, TypeCheckNode[] argTypes, InteropLibrary interop, HostContext context, SingleMethod overload) {
487495
if (args.length != argTypes.length) {
488496
return false;
489497
}

0 commit comments

Comments
 (0)