Skip to content

Commit 169b889

Browse files
committed
[GR-71852] Ristretto: support call lowering.
PullRequest: graal/22895
2 parents 457a758 + 9fbb91a commit 169b889

10 files changed

Lines changed: 198 additions & 31 deletions

File tree

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Map;
3333
import java.util.function.Predicate;
3434

35+
import org.graalvm.nativeimage.c.function.CFunctionPointer;
3536
import org.graalvm.word.LocationIdentity;
3637

3738
import com.oracle.svm.core.FrameAccess;
@@ -114,6 +115,7 @@
114115
import jdk.graal.compiler.nodes.type.StampTool;
115116
import jdk.graal.compiler.options.OptionValues;
116117
import jdk.graal.compiler.phases.util.Providers;
118+
import jdk.graal.compiler.word.Word;
117119
import jdk.vm.ci.code.CallingConvention;
118120
import jdk.vm.ci.meta.DeoptimizationAction;
119121
import jdk.vm.ci.meta.DeoptimizationReason;
@@ -428,25 +430,43 @@ public void lower(FixedNode node, LoweringTool tool) {
428430
*/
429431
loweredCallTarget = createUnreachableCallTarget(tool, node, parameters, callTarget.returnStamp(), signature, method, callType, invokeKind);
430432
} else {
431-
/*
432-
* In runtime-compiled code, we emit indirect calls via the respective heap
433-
* objects to avoid patching and creating trampolines.
434-
*/
435-
JavaConstant codeInfo = SubstrateObjectConstant.forObject(targetMethod.getImageCodeInfo());
436-
ValueNode codeInfoConstant = ConstantNode.forConstant(codeInfo, tool.getMetaAccess(), graph);
437-
ValueNode codeStartFieldOffset = ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), knownOffsets.getImageCodeInfoCodeStartOffset(), graph);
438-
AddressNode codeStartField = graph.unique(new OffsetAddressNode(codeInfoConstant, codeStartFieldOffset));
439-
/*
440-
* Uses ANY_LOCATION because runtime-compiled code can be persisted and
441-
* loaded in a process where image code is located elsewhere.
442-
*/
443-
ReadNode codeStart = graph.add(new ReadNode(codeStartField, LocationIdentity.ANY_LOCATION, FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN));
444-
ValueNode offset = ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), targetMethod.getImageCodeOffset(), graph);
445-
AddressNode address = graph.unique(new OffsetAddressNode(codeStart, offset));
446-
447-
loweredCallTarget = graph.add(new SubstrateIndirectCallTargetNode(
448-
address, parameters.toArray(new ValueNode[parameters.size()]), callTarget.returnStamp(), signature, targetMethod, callType, invokeKind));
449-
graph.addBeforeFixed(node, codeStart);
433+
// This needs to have a static type of CFunctionPointer to workaround
434+
// GR-72464
435+
CFunctionPointer rawAdrConstant = targetMethod.getAOTEntrypoint();
436+
assert !SubstrateUtil.HOSTED;
437+
if (rawAdrConstant == Word.nullPointer()) {
438+
/*
439+
* In runtime-compiled code, we emit indirect calls via the respective
440+
* heap objects to avoid patching and creating trampolines.
441+
*/
442+
JavaConstant codeInfo = SubstrateObjectConstant.forObject(targetMethod.getImageCodeInfo());
443+
ValueNode codeInfoConstant = ConstantNode.forConstant(codeInfo, tool.getMetaAccess(), graph);
444+
ValueNode codeStartFieldOffset = ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), knownOffsets.getImageCodeInfoCodeStartOffset(), graph);
445+
AddressNode codeStartField = graph.unique(new OffsetAddressNode(codeInfoConstant, codeStartFieldOffset));
446+
/*
447+
* Uses ANY_LOCATION because runtime-compiled code can be persisted and
448+
* loaded in a process where image code is located elsewhere.
449+
*/
450+
ReadNode codeStart = graph.add(new ReadNode(codeStartField, LocationIdentity.ANY_LOCATION, FrameAccess.getWordStamp(), BarrierType.NONE, MemoryOrderMode.PLAIN));
451+
ValueNode offset = ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), targetMethod.getImageCodeOffset(), graph);
452+
AddressNode address = graph.unique(new OffsetAddressNode(codeStart, offset));
453+
454+
loweredCallTarget = graph.add(new SubstrateIndirectCallTargetNode(
455+
address, parameters.toArray(new ValueNode[parameters.size()]), callTarget.returnStamp(), signature, targetMethod, callType, invokeKind));
456+
graph.addBeforeFixed(node, codeStart);
457+
} else {
458+
/*
459+
* Directly lower to a call based on the raw address, no offset address
460+
* required.
461+
*/
462+
assert SubstrateOptions.useRistretto();
463+
final long untrackedMethodAdr = rawAdrConstant.rawValue();
464+
GraalError.guarantee(untrackedMethodAdr != 0L, "runtime-loaded method has no valid entry point: %s", targetMethod);
465+
final ValueNode base = graph.addWithoutUnique(new FloatingWordCastNode(tool.getStampProvider().createMethodStamp(), ConstantNode.forLong(untrackedMethodAdr, graph)));
466+
loweredCallTarget = graph.add(new SubstrateIndirectCallTargetNode(
467+
base, parameters.toArray(new ValueNode[parameters.size()]), callTarget.returnStamp(), signature,
468+
targetMethod, callType, invokeKind));
469+
}
450470
}
451471
} else if (implementations.length == 0 && isClosedTypeWorld) {
452472
/*
@@ -459,7 +479,6 @@ public void lower(FixedNode node, LoweringTool tool) {
459479
* a subsequent dead code elimination pass.
460480
*/
461481
loweredCallTarget = createUnreachableCallTarget(tool, node, parameters, callTarget.returnStamp(), signature, method, callType, invokeKind);
462-
463482
} else {
464483
StampProvider stampProvider = runtimeConfig.getProviders().getStampProvider();
465484
LoadHubNode hub = graph.unique(new LoadHubNode(stampProvider, graph.addOrUnique(PiNode.create(receiver, nullCheck))));

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedMethod.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ public interface SharedMethod extends ResolvedJavaMethod {
9999
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
100100
int getImageCodeDeoptOffset();
101101

102-
/** Always call this method indirectly, even if it is normally called directly. */
102+
/**
103+
* Always call this method indirectly, even if it is normally called directly.
104+
*/
103105
boolean forceIndirectCall();
104106

105107
/**
@@ -108,4 +110,17 @@ public interface SharedMethod extends ResolvedJavaMethod {
108110
*/
109111
@Override
110112
boolean isDeclared();
113+
114+
/**
115+
* Returns a function pointer to the method if it can be called directly without any dispatch.
116+
* <p>
117+
* This method should be overridden in implementations to provide raw access to the direct
118+
* address of this method. This is solely reserved for types present during image building and
119+
* should only be used at runtime for just-in-time compiled code calling into the image built
120+
* method.
121+
*
122+
* @return an AOT compiled entry point of this method or {@code Word.nullPointer()} if no
123+
* compiled entry point is available.
124+
*/
125+
MethodPointer getAOTEntrypoint();
111126
}

substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.HashMap;
2929
import java.util.Map;
3030

31-
import jdk.graal.compiler.word.Word;
3231
import org.graalvm.collections.EconomicSet;
3332
import org.graalvm.nativeimage.ImageSingletons;
3433
import org.graalvm.nativeimage.c.type.CTypeConversion;
@@ -75,6 +74,7 @@
7574
import jdk.graal.compiler.debug.DebugContext;
7675
import jdk.graal.compiler.debug.Indent;
7776
import jdk.graal.compiler.truffle.TruffleCompilerImpl;
77+
import jdk.graal.compiler.word.Word;
7878
import jdk.vm.ci.code.TargetDescription;
7979
import jdk.vm.ci.code.site.Call;
8080
import jdk.vm.ci.code.site.ConstantReference;

substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.oracle.svm.core.graal.phases.SubstrateSafepointInsertionPhase;
5353
import com.oracle.svm.core.heap.UnknownObjectField;
5454
import com.oracle.svm.core.heap.UnknownPrimitiveField;
55+
import com.oracle.svm.core.meta.MethodPointer;
5556
import com.oracle.svm.core.meta.SharedMethod;
5657
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
5758
import com.oracle.svm.core.util.HostedStringDeduplication;
@@ -60,6 +61,7 @@
6061

6162
import jdk.graal.compiler.api.replacements.Snippet;
6263
import jdk.graal.compiler.core.common.util.TypeConversion;
64+
import jdk.graal.compiler.word.Word;
6365
import jdk.vm.ci.meta.Constant;
6466
import jdk.vm.ci.meta.ConstantPool;
6567
import jdk.vm.ci.meta.DefaultProfilingInfo;
@@ -512,6 +514,11 @@ public SpeculationLog getSpeculationLog() {
512514
throw intentionallyUnimplemented(); // ExcludeFromJacocoGeneratedReport
513515
}
514516

517+
@Override
518+
public MethodPointer getAOTEntrypoint() {
519+
return Word.nullPointer();
520+
}
521+
515522
@Override
516523
public String toString() {
517524
return "SubstrateMethod<" + format("%h.%n") + ">";

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,4 +696,9 @@ public Collection<MultiMethod> getAllMultiMethods() {
696696
return multiMethodMap.values();
697697
}
698698
}
699+
700+
@Override
701+
public MethodPointer getAOTEntrypoint() {
702+
throw VMError.intentionallyUnimplemented();
703+
}
699704
}

substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/profile/MethodProfile.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
3131
import java.util.List;
32+
import java.util.function.Function;
3233

3334
import com.oracle.svm.core.hub.DynamicHub;
3435
import com.oracle.svm.interpreter.metadata.Bytecodes;
36+
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType;
3537

3638
import jdk.graal.compiler.bytecode.BytecodeStream;
3739
import jdk.graal.compiler.nodes.IfNode;
@@ -83,12 +85,12 @@ public final class MethodProfile {
8385

8486
private boolean isMature;
8587

86-
public MethodProfile(ResolvedJavaMethod method) {
88+
public MethodProfile(ResolvedJavaMethod method, Function<InterpreterResolvedJavaType, ResolvedJavaType> ristrettoTypeSupplier) {
8789
this.method = method;
88-
this.profiles = buildProfiles(method);
90+
this.profiles = buildProfiles(method, ristrettoTypeSupplier);
8991
}
9092

91-
private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) {
93+
private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method, Function<InterpreterResolvedJavaType, ResolvedJavaType> ristrettoTypeSupplier) {
9294
BytecodeStream stream = new BytecodeStream(method.getCode());
9395
stream.setBCI(0);
9496
List<InterpreterProfile> allProfiles = new ArrayList<>();
@@ -104,7 +106,7 @@ private static InterpreterProfile[] buildProfiles(ResolvedJavaMethod method) {
104106
allProfiles.add(new BranchProfile(bci));
105107
}
106108
if (Bytecodes.isTypeProfiled(opcode)) {
107-
allProfiles.add(new TypeProfile(bci));
109+
allProfiles.add(new TypeProfile(bci, ristrettoTypeSupplier));
108110
}
109111
// TODO GR-71799 - backedge / goto profiles
110112
stream.next();
@@ -317,11 +319,18 @@ public static class TypeProfile extends CountingProfile {
317319
*/
318320
private final long[] counts;
319321

320-
public TypeProfile(int bci) {
322+
/**
323+
* We profile interpreter types but when we export the information to the compiler as a type
324+
* profile we need to use ristretto types.
325+
*/
326+
private final Function<InterpreterResolvedJavaType, ResolvedJavaType> ristrettoTypeSupplier;
327+
328+
public TypeProfile(int bci, Function<InterpreterResolvedJavaType, ResolvedJavaType> ristrettoTypeSupplier) {
321329
super(bci);
322330
final int typeProfileWidth = InterpreterProfilingOptions.JITProfileTypeProfileWidth.getValue();
323331
types = new ResolvedJavaType[typeProfileWidth];
324332
counts = new long[typeProfileWidth];
333+
this.ristrettoTypeSupplier = ristrettoTypeSupplier;
325334
}
326335

327336
/**
@@ -411,7 +420,12 @@ public JavaTypeProfile toTypeProfile() {
411420
double p = counts[i];
412421
p = p / counter;
413422
totalProbability += p;
414-
ptypes[i] = new JavaTypeProfile.ProfiledType(types[i], p);
423+
/*
424+
* When we give the profile out we want it to be ristretto types only. Consumers,
425+
* especially the compiler, will require ristretto view of the JVMCI data.
426+
*/
427+
final ResolvedJavaType rType = ((InterpreterResolvedJavaType) types[i]).getRistrettoType(ristrettoTypeSupplier);
428+
ptypes[i] = new JavaTypeProfile.ProfiledType(rType, p);
415429
}
416430
Arrays.sort(ptypes);
417431
double notRecordedTypeProbability = profiledTypeCount < types.length ? 0.0 : Math.min(1.0, Math.max(0.0, 1.0 - totalProbability));

substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/RuntimeInterpreterConstantPool.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@
4141
import com.oracle.svm.espresso.classfile.descriptors.Symbol;
4242
import com.oracle.svm.espresso.classfile.descriptors.Type;
4343
import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols;
44+
import com.oracle.svm.interpreter.metadata.Bytecodes;
4445
import com.oracle.svm.interpreter.metadata.CremaResolvedObjectType;
4546
import com.oracle.svm.interpreter.metadata.InterpreterConstantPool;
4647
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField;
4748
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod;
4849
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType;
4950
import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType;
5051

52+
import jdk.vm.ci.meta.JavaType;
5153
import jdk.vm.ci.meta.UnresolvedJavaField;
5254
import jdk.vm.ci.meta.UnresolvedJavaMethod;
5355
import jdk.vm.ci.meta.UnresolvedJavaType;
@@ -339,4 +341,34 @@ private static Class<?> resolveSymbolAndAccessCheck(InterpreterResolvedObjectTyp
339341
return clazz;
340342
}
341343
}
344+
345+
@Override
346+
public JavaType lookupReferencedType(int cpi, int opcode) {
347+
int declaringClassCPI = -1;
348+
switch (opcode) {
349+
case Bytecodes.CHECKCAST:
350+
case Bytecodes.INSTANCEOF:
351+
case Bytecodes.NEW:
352+
case Bytecodes.ANEWARRAY:
353+
case Bytecodes.MULTIANEWARRAY:
354+
case Bytecodes.LDC:
355+
case Bytecodes.LDC_W:
356+
case Bytecodes.LDC2_W:
357+
declaringClassCPI = cpi;
358+
break;
359+
case Bytecodes.GETSTATIC:
360+
case Bytecodes.PUTSTATIC:
361+
case Bytecodes.GETFIELD:
362+
case Bytecodes.PUTFIELD:
363+
case Bytecodes.INVOKEVIRTUAL:
364+
case Bytecodes.INVOKESPECIAL:
365+
case Bytecodes.INVOKESTATIC:
366+
case Bytecodes.INVOKEINTERFACE:
367+
declaringClassCPI = memberClassIndex(cpi);
368+
break;
369+
default:
370+
throw VMError.shouldNotReachHere("Unexpected opcode: " + opcode); // ExcludeFromJacocoGeneratedReport
371+
}
372+
return findClassAt(declaringClassCPI);
373+
}
342374
}

substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ristretto/RistrettoUtils.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929

3030
import org.graalvm.collections.EconomicMap;
3131
import org.graalvm.nativeimage.ImageSingletons;
32+
import org.graalvm.nativeimage.c.function.CFunctionPointer;
3233
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
3334

3435
import com.oracle.svm.common.option.CommonOptionParser;
36+
import com.oracle.svm.core.Uninterruptible;
3537
import com.oracle.svm.core.deopt.SubstrateInstalledCode;
3638
import com.oracle.svm.core.deopt.SubstrateSpeculationLog;
3739
import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier;
@@ -46,6 +48,8 @@
4648
import com.oracle.svm.graal.meta.SubstrateMethod;
4749
import com.oracle.svm.hosted.image.PreserveOptionsSupport;
4850
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod;
51+
import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType;
52+
import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType;
4953
import com.oracle.svm.interpreter.ristretto.compile.RistrettoGraphBuilderPhase;
5054
import com.oracle.svm.interpreter.ristretto.compile.RistrettoNoDeoptPhase;
5155
import com.oracle.svm.interpreter.ristretto.meta.RistrettoMethod;
@@ -73,6 +77,8 @@
7377
import jdk.graal.compiler.phases.tiers.Suites;
7478
import jdk.graal.compiler.phases.util.Providers;
7579
import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
80+
import jdk.graal.compiler.word.Word;
81+
import jdk.vm.ci.code.InstalledCode;
7682
import jdk.vm.ci.meta.ResolvedJavaMethod;
7783
import jdk.vm.ci.meta.ResolvedJavaType;
7884
import jdk.vm.ci.meta.SpeculationLog;
@@ -141,6 +147,17 @@ public static CompilationResult compile(DebugContext debug, final SubstrateMetho
141147
return doCompile(debug, RuntimeCompilationSupport.getRuntimeConfig(), RuntimeCompilationSupport.getLIRSuites(), method);
142148
}
143149

150+
/**
151+
* DEBUG ONLY facility to compile the given method and install the resulting code. Normally done
152+
* over {@link com.oracle.svm.interpreter.ristretto.profile.RistrettoCompilationManager}. Use
153+
* only for testing.
154+
*/
155+
public static SubstrateInstalledCodeImpl compileAndInstallInCrema(RistrettoMethod method) {
156+
SubstrateInstalledCodeImpl ic = compileAndInstall(method, () -> new SubstrateInstalledCodeImpl(method));
157+
method.installedCode = ic;
158+
return ic;
159+
}
160+
144161
public static StructuredGraph parseOnly(SubstrateMethod method) {
145162
if (method instanceof RistrettoMethod rMethod) {
146163
final RuntimeConfiguration runtimeConfig = RuntimeCompilationSupport.getRuntimeConfig();
@@ -282,6 +299,44 @@ private static void parseFromBytecode(StructuredGraph graph, RuntimeConfiguratio
282299
assert graph.getNodeCount() > 1 : "Must have nodes after parsing";
283300
}
284301

302+
/**
303+
* DEBUG ONLY facility to log the virtual method table (vtable) of an interpreter type. This
304+
* method is highly unsafe, does not check for concurrent updates to any data structures inside
305+
* {@code iType} and it is also not {@link Uninterruptible}.
306+
* <p>
307+
* Use with extreme caution and only from places where it is guaranteed that no concurrent
308+
* updates to the data inside {@code iType} happens.
309+
*/
310+
public static void logVTable(InterpreterResolvedJavaType iType) {
311+
if (!(iType instanceof InterpreterResolvedObjectType t)) {
312+
return;
313+
}
314+
Log.log().string("Dumping vtable ").string(t.toClassName()).newline();
315+
InterpreterResolvedJavaMethod[] table = t.getVtable();
316+
if (table == null) {
317+
Log.log().string("No vtable found").newline();
318+
return;
319+
}
320+
for (int i = 0; i < table.length; i++) {
321+
InterpreterResolvedJavaMethod method = table[i];
322+
CFunctionPointer jitEntryPoint = Word.nullPointer();
323+
RistrettoMethod rMethod = (RistrettoMethod) method.getRistrettoMethod();
324+
if (rMethod == null) {
325+
continue;
326+
}
327+
InstalledCode ic = rMethod.installedCode;
328+
if (ic != null && ic.isValid()) {
329+
/*
330+
* A JIT compiled version is available, execute this one instead. This could be more
331+
* optimized, see GR-71160.
332+
*/
333+
jitEntryPoint = Word.pointer(ic.getEntryPoint());
334+
}
335+
Log.log().string("\tslot=").signed(method.getVTableIndex()).string(" -> ").string(method.getDeclaringClass().toClassName()).string("::").string(method.getName()).string(", AOT addr=")
336+
.hex(method.getNativeEntryPoint()).string(", JIT addr=").hex(jitEntryPoint).newline();
337+
}
338+
}
339+
285340
public static final class TestingBackdoor {
286341
private TestingBackdoor() {
287342
// this type should never be allocated

0 commit comments

Comments
 (0)