Skip to content

Commit 21df2c0

Browse files
committed
[GR-75319] Lazy builtin calltargets.
PullRequest: graalpython/4440
2 parents 71b7784 + 02fba27 commit 21df2c0

42 files changed

Lines changed: 289 additions & 355 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 53 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -314,18 +314,18 @@ public boolean isSingleContext() {
314314
public final Assumption noInteropTypeRegisteredAssumption = Truffle.getRuntime().createAssumption("No class for interop registered");
315315

316316
/**
317-
* Call targets that are populated during native-image build time and then only read at image
317+
* Root nodes that are populated during native-image build time and then only read at image
318318
* runtime. Using {@link EconomicMap} avoids embedding a large number of
319319
* {@link java.util.concurrent.ConcurrentHashMap.Node} objects into the image heap.
320320
*/
321-
private final EconomicMap<Object, RootCallTarget> imageBuildtimeCachedCallTargets = ImageInfo.inImageCode() ? EconomicMap.create() : null;
321+
private final EconomicMap<Object, RootNode> imageBuildtimeCachedRootNodes = ImageInfo.inImageCode() ? EconomicMap.create() : null;
322322
/**
323-
* Call targets added after image startup, or all cached call targets when running on the JVM.
323+
* Root nodes added after image startup, or all cached root nodes when running on the JVM.
324324
*/
325-
private final ConcurrentHashMap<Object, RootCallTarget> runtimeCachedCallTargets = new ConcurrentHashMap<>();
325+
private final ConcurrentHashMap<Object, RootNode> runtimeCachedRootNodes = new ConcurrentHashMap<>();
326326

327-
@CompilationFinal(dimensions = 1) private final RootCallTarget[] builtinSlotsCallTargets;
328327
@CompilationFinal(dimensions = 1) private RootCallTarget[] capiCallTargets;
328+
@CompilationFinal(dimensions = 1) private final RootNode[] builtinSlotsRootNodes;
329329

330330
/**
331331
* We cannot initialize call targets in language ctor and the next suitable hook is context
@@ -388,7 +388,7 @@ public PythonLanguage() {
388388
if (PythonBuiltinClassType.PythonClass.getSlots() == null) {
389389
throw new IllegalStateException("Slots must be initialized in PythonBuiltinClassType static initializer");
390390
}
391-
builtinSlotsCallTargets = new RootCallTarget[TpSlot.getBuiltinsCallTargetsCount()];
391+
builtinSlotsRootNodes = new RootNode[TpSlot.getBuiltinsCallTargetsCount()];
392392
}
393393

394394
/**
@@ -1068,12 +1068,14 @@ private Shape createBuiltinShape(PythonBuiltinClassType type, int ordinal) {
10681068
}
10691069

10701070
public RootCallTarget getBuiltinSlotCallTarget(int index) {
1071-
return builtinSlotsCallTargets[index];
1071+
RootNode rootNode = builtinSlotsRootNodes[index];
1072+
assert rootNode != null : index;
1073+
return rootNode.getCallTarget();
10721074
}
10731075

1074-
public void setBuiltinSlotCallTarget(int index, RootCallTarget callTarget) {
1076+
public void setBuiltinSlotRootNode(int index, RootNode rootNode) {
10751077
VarHandle.storeStoreFence();
1076-
builtinSlotsCallTargets[index] = callTarget;
1078+
builtinSlotsRootNodes[index] = rootNode;
10771079
}
10781080

10791081
public RootCallTarget getCapiCallTarget(int index) {
@@ -1094,140 +1096,94 @@ public void setCapiCallTarget(int index, RootCallTarget ct) {
10941096
capiCallTargets[index] = ct;
10951097
}
10961098

1097-
/**
1098-
* Caches call target that wraps a node that is not parametrized, i.e., has only a parameterless
1099-
* ctor and all its instances implement the same logic. Parametrized nodes must include the
1100-
* parameters that alter their behavior as part of the cache key.
1101-
*/
1102-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> key) {
1103-
// It's complicated with RootNodes, but regular nodes should have only parameterless ctor to
1104-
// be appropriate keys for the cache
1099+
public <T extends RootNode> T createCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<? extends Node> key) {
11051100
assert RootNode.class.isAssignableFrom(key) || key.getConstructors().length <= 1;
11061101
assert RootNode.class.isAssignableFrom(key) || key.getConstructors().length == 0 || key.getConstructors()[0].getParameterCount() == 0;
1107-
return createCachedCallTargetUnsafe(rootNodeFunction, key, true);
1108-
}
1109-
1110-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Enum<?> key) {
1111-
return createCachedCallTargetUnsafe(rootNodeFunction, key, true);
1102+
return createCachedRootNodeUnsafe(rootNodeFunction, key, true);
11121103
}
11131104

1114-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, int key) {
1115-
return createCachedCallTargetUnsafe(rootNodeFunction, key, true);
1105+
public RootNode createCachedRootNode(Function<PythonLanguage, RootNode> rootNodeFunction, Enum<?> key) {
1106+
return createCachedRootNodeUnsafe(rootNodeFunction, key, true);
11161107
}
11171108

1118-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, String key) {
1119-
// for builtins: name is needed to distinguish builtins that share the same underlying node
1120-
// in general: a String may be parameter of the node wrapped in the root node or the root
1121-
// node itself, there must be finite number of strings that can appear here (i.e., must not
1122-
// be dynamically generated unless their number is bounded).
1123-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass, key);
1109+
public <T extends RootNode> T createCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<? extends Node> nodeClass, String key) {
1110+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass, key);
11241111
}
11251112

1126-
// Variant that should be called at most once per context, because it does not cache the target
1127-
// in single context mode
1128-
public RootCallTarget initBuiltinCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, String key) {
1129-
return createCachedCallTargetUnsafe(rootNodeFunction, false, nodeClass, key);
1113+
// Variant that should be called at most once per context, because it does not cache the root
1114+
// node in single context mode
1115+
public <T extends RootNode> T initBuiltinRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<? extends Node> nodeClass, String key) {
1116+
return createCachedRootNodeUnsafe(rootNodeFunction, false, nodeClass, key);
11301117
}
11311118

1132-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, TruffleString key) {
1133-
// See the String overload
1134-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass, key);
1119+
public RootNode createCachedRootNode(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, TruffleString key) {
1120+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass, key);
11351121
}
11361122

1137-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, int key) {
1138-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass, key);
1123+
public RootNode createCachedRootNode(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass, int key) {
1124+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass, key);
11391125
}
11401126

1141-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass1, Class<?> nodeClass2, String name) {
1142-
// for slot call targets and wrappers: the root node may be wrapping a helper wrapper node
1143-
// implementing the slot wrapper logic and the bare slot node itself
1144-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass1, nodeClass2, name);
1127+
public <T extends RootNode> T createCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<? extends Node> nodeClass1, Class<?> nodeClass2, String name) {
1128+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass1, nodeClass2, name);
11451129
}
11461130

1147-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<? extends Node> nodeClass1, Class<?> nodeClass2, PythonBuiltinClassType type, String name) {
1148-
// for slot wrappers: the type is used for validation of "self" type
1149-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass1, nodeClass2, type, name);
1131+
public <T extends RootNode> T createCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<? extends Node> nodeClass1, Class<?> nodeClass2, PythonBuiltinClassType type, String name) {
1132+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass1, nodeClass2, type, name);
11501133
}
11511134

1152-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, CodeUnit key) {
1153-
return createCachedCallTargetUnsafe(rootNodeFunction, true, key);
1135+
public <T extends RootNode> T createCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, CodeUnit key) {
1136+
return createCachedRootNodeUnsafe(rootNodeFunction, true, key);
11541137
}
11551138

1156-
/**
1157-
* Caches call targets for external C functions created by extensions at runtime.
1158-
* <p>
1159-
* For the time being, we assume finite/limited number of extensions and their external
1160-
* functions. This may hold onto call targets created by one extension used in a context that
1161-
* was closed in the meanwhile and no other context ever loads the extension.
1162-
*/
1163-
public RootCallTarget createCachedExternalFunWrapperCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction,
1139+
public <T extends RootNode> T createCachedExternalFunWrapperRootNode(Function<PythonLanguage, T> rootNodeFunction,
11641140
Class<? extends RootNode> klass, Enum<?> signature, TruffleString name,
11651141
boolean doArgumentAndResultConversion, boolean isStatic) {
1166-
return createCachedCallTargetUnsafe(rootNodeFunction, true, klass, signature, name, doArgumentAndResultConversion, isStatic);
1142+
return createCachedRootNodeUnsafe(rootNodeFunction, true, klass, signature, name, doArgumentAndResultConversion, isStatic);
11671143
}
11681144

1169-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Enum<?> signature, TruffleString name,
1170-
boolean doArgumentAndResultConversion) {
1171-
return createCachedCallTargetUnsafe(rootNodeFunction, true, signature, name, doArgumentAndResultConversion);
1145+
public <T extends RootNode> T createStructSeqIndexedMemberAccessCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, int memberIndex) {
1146+
return createCachedRootNodeUnsafe(rootNodeFunction, true, StructSequence.class, memberIndex);
11721147
}
11731148

1174-
public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Enum<?> signature, TruffleString name) {
1175-
return createCachedCallTargetUnsafe(rootNodeFunction, true, signature, name);
1149+
public <T extends RootNode> T createCachedPropAccessRootNode(Function<PythonLanguage, T> rootNodeFunction, Class<?> nodeClass, String name, int type, int offset) {
1150+
return createCachedRootNodeUnsafe(rootNodeFunction, true, nodeClass, name, type, offset);
11761151
}
11771152

1178-
public RootCallTarget createStructSeqIndexedMemberAccessCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, int memberIndex) {
1179-
return createCachedCallTargetUnsafe(rootNodeFunction, true, StructSequence.class, memberIndex);
1180-
}
1181-
1182-
public RootCallTarget createCachedPropAccessCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Class<?> nodeClass, String name, int type, int offset) {
1183-
// For the time being, we assume finite/limited number of cext/hpy types members, their
1184-
// types and offsets
1185-
return createCachedCallTargetUnsafe(rootNodeFunction, true, nodeClass, name, type, offset);
1186-
}
1187-
1188-
/**
1189-
* Keys in any caches held by {@link PythonLanguage} must be context independent objects and
1190-
* there must be either finite number of their instances, or if the key is a context independent
1191-
* mirror of some runtime data structure, it must be cached weakly. This call targets cache is
1192-
* strong.
1193-
* <p>
1194-
* To avoid memory leaks, all key types must be known to have finite number of possible
1195-
* instances. Public methods for adding to the cache must take concrete key type(s) so that all
1196-
* possible cache keys are explicit and documented.
1197-
*/
1198-
private RootCallTarget createCachedCallTargetUnsafe(Function<PythonLanguage, RootNode> rootNodeFunction, Object key, boolean cacheInSingleContext) {
1153+
private <T extends RootNode> T createCachedRootNodeUnsafe(Function<PythonLanguage, T> rootNodeFunction, Object key, boolean cacheInSingleContext) {
11991154
CompilerAsserts.neverPartOfCompilation();
12001155
if (cacheInSingleContext || !singleContext) {
1201-
return getOrCreateCachedCallTarget(rootNodeFunction, key);
1156+
return getOrCreateCachedRootNode(rootNodeFunction, key);
12021157
} else {
1203-
return PythonUtils.getOrCreateCallTarget(rootNodeFunction.apply(this));
1158+
return rootNodeFunction.apply(this);
12041159
}
12051160
}
12061161

1207-
private RootCallTarget createCachedCallTargetUnsafe(Function<PythonLanguage, RootNode> rootNodeFunction, boolean cacheInSingleContext, Object... cacheKeys) {
1208-
return createCachedCallTargetUnsafe(rootNodeFunction, Arrays.asList(cacheKeys), cacheInSingleContext);
1162+
private <T extends RootNode> T createCachedRootNodeUnsafe(Function<PythonLanguage, T> rootNodeFunction, boolean cacheInSingleContext, Object... cacheKeys) {
1163+
return createCachedRootNodeUnsafe(rootNodeFunction, Arrays.asList(cacheKeys), cacheInSingleContext);
12091164
}
12101165

1211-
private RootCallTarget getOrCreateCachedCallTarget(Function<PythonLanguage, RootNode> rootNodeFunction, Object key) {
1166+
@SuppressWarnings("unchecked")
1167+
private <T extends RootNode> T getOrCreateCachedRootNode(Function<PythonLanguage, T> rootNodeFunction, Object key) {
12121168
CompilerAsserts.neverPartOfCompilation();
12131169
if (ImageInfo.inImageRuntimeCode()) {
1214-
RootCallTarget preinitialized = imageBuildtimeCachedCallTargets.get(key);
1170+
RootNode preinitialized = imageBuildtimeCachedRootNodes.get(key);
12151171
if (preinitialized != null) {
1216-
return preinitialized;
1172+
return (T) preinitialized;
12171173
}
1218-
return runtimeCachedCallTargets.computeIfAbsent(key, k -> PythonUtils.getOrCreateCallTarget(rootNodeFunction.apply(this)));
1174+
return (T) runtimeCachedRootNodes.computeIfAbsent(key, k -> rootNodeFunction.apply(this));
12191175
}
12201176
if (ImageInfo.inImageBuildtimeCode()) {
1221-
synchronized (imageBuildtimeCachedCallTargets) {
1222-
RootCallTarget cached = imageBuildtimeCachedCallTargets.get(key);
1177+
synchronized (imageBuildtimeCachedRootNodes) {
1178+
RootNode cached = imageBuildtimeCachedRootNodes.get(key);
12231179
if (cached == null) {
1224-
cached = PythonUtils.getOrCreateCallTarget(rootNodeFunction.apply(this));
1225-
imageBuildtimeCachedCallTargets.put(key, cached);
1180+
cached = rootNodeFunction.apply(this);
1181+
imageBuildtimeCachedRootNodes.put(key, cached);
12261182
}
1227-
return cached;
1183+
return (T) cached;
12281184
}
12291185
}
1230-
return runtimeCachedCallTargets.computeIfAbsent(key, k -> PythonUtils.getOrCreateCallTarget(rootNodeFunction.apply(this)));
1186+
return (T) runtimeCachedRootNodes.computeIfAbsent(key, k -> rootNodeFunction.apply(this));
12311187
}
12321188

12331189
@Override

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltins.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
import com.oracle.graal.python.builtins.modules.ImpModuleBuiltins.ExecBuiltin;
4343
import com.oracle.graal.python.builtins.objects.PNone;
4444
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
45+
import com.oracle.graal.python.builtins.objects.function.Signature;
4546
import com.oracle.graal.python.builtins.objects.module.PythonModule;
4647
import com.oracle.graal.python.builtins.objects.object.PythonObject;
4748
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
4849
import com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode;
4950
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
5051
import com.oracle.graal.python.runtime.object.PFactory;
5152
import com.oracle.graal.python.util.BiConsumer;
52-
import com.oracle.truffle.api.RootCallTarget;
5353
import com.oracle.truffle.api.TruffleLanguage.Env;
5454
import com.oracle.truffle.api.dsl.NodeFactory;
5555
import com.oracle.truffle.api.strings.TruffleString;
@@ -108,11 +108,12 @@ public void initialize(Python3Core core) {
108108
}
109109
TruffleString tsName = toInternedTruffleStringUncached(builtin.name());
110110
PythonLanguage language = core.getLanguage();
111-
RootCallTarget callTarget = language.initBuiltinCallTarget(l -> new BuiltinFunctionRootNode(l, builtin, factory, declaresExplicitSelf), factory.getNodeClass(),
112-
builtin.name());
113111
Object builtinDoc = builtin.doc().isEmpty() ? PNone.NONE : toTruffleStringUncached(builtin.doc());
114-
int flags = PBuiltinFunction.getFlags(builtin, callTarget);
115-
PBuiltinFunction function = PFactory.createBuiltinFunction(language, tsName, null, numDefaults(builtin), flags, callTarget);
112+
BuiltinFunctionRootNode rootNode = language.initBuiltinRootNode(l -> new BuiltinFunctionRootNode(l, builtin, factory, declaresExplicitSelf),
113+
factory.getNodeClass(), builtin.name());
114+
Signature signature = rootNode.getSignature();
115+
int flags = PBuiltinFunction.getFlags(builtin, signature);
116+
PBuiltinFunction function = PFactory.createBuiltinFunction(language, tsName, null, numDefaults(builtin), flags, rootNode);
116117
function.setAttribute(T___DOC__, builtinDoc);
117118
BoundBuiltinCallable<?> callable = function;
118119
if (builtin.isGetter() || builtin.isSetter()) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/AtexitModuleBuiltins.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2026, 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
@@ -66,7 +66,6 @@
6666
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
6767
import com.oracle.graal.python.runtime.exception.PException;
6868
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
69-
import com.oracle.truffle.api.RootCallTarget;
7069
import com.oracle.truffle.api.TruffleLanguage;
7170
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
7271
import com.oracle.truffle.api.dsl.NodeFactory;
@@ -135,8 +134,8 @@ private static void handleException(PythonContext context, PException e) {
135134
@Specialization
136135
static Object register(Object callable, Object[] arguments, PKeyword[] keywords) {
137136
PythonContext context = PythonContext.get(null);
138-
RootCallTarget callTarget = context.getLanguage().createCachedCallTarget(AtExitRootNode::new, AtExitRootNode.class);
139-
context.registerAtexitHook(callable, arguments, keywords, callTarget);
137+
RootNode rootNode = context.getLanguage().createCachedRootNode(AtExitRootNode::new, AtExitRootNode.class);
138+
context.registerAtexitHook(callable, arguments, keywords, rootNode);
140139
return callable;
141140
}
142141
}

0 commit comments

Comments
 (0)