4848import java .util .concurrent .Semaphore ;
4949import java .util .logging .Level ;
5050
51+ import org .graalvm .collections .EconomicMap ;
5152import org .graalvm .home .Version ;
5253import org .graalvm .nativeimage .ImageInfo ;
5354import org .graalvm .options .OptionDescriptors ;
@@ -311,12 +312,15 @@ public boolean isSingleContext() {
311312 public final Assumption noInteropTypeRegisteredAssumption = Truffle .getRuntime ().createAssumption ("No class for interop registered" );
312313
313314 /**
314- * A thread-safe map to retrieve (and cache) singleton instances of call targets, e.g., for
315- * Arithmetic operations, wrappers, named cext functions, etc. This reduces the number of call
316- * targets and allows AST sharing across contexts. The key in this map is either a single value
317- * or a list of values.
315+ * Call targets that are populated during native-image build time and then only read at image
316+ * runtime. Using {@link EconomicMap} avoids embedding a large number of
317+ * {@link java.util.concurrent.ConcurrentHashMap.Node} objects into the image heap.
318318 */
319- private final ConcurrentHashMap <Object , RootCallTarget > cachedCallTargets = new ConcurrentHashMap <>();
319+ private final EconomicMap <Object , RootCallTarget > imageBuildtimeCachedCallTargets = ImageInfo .inImageCode () ? EconomicMap .create () : null ;
320+ /**
321+ * Call targets added after image startup, or all cached call targets when running on the JVM.
322+ */
323+ private final ConcurrentHashMap <Object , RootCallTarget > runtimeCachedCallTargets = new ConcurrentHashMap <>();
320324
321325 @ CompilationFinal (dimensions = 1 ) private final RootCallTarget [] builtinSlotsCallTargets ;
322326
@@ -1157,7 +1161,7 @@ public RootCallTarget createCachedPropAccessCallTarget(Function<PythonLanguage,
11571161 private RootCallTarget createCachedCallTargetUnsafe (Function <PythonLanguage , RootNode > rootNodeFunction , Object key , boolean cacheInSingleContext ) {
11581162 CompilerAsserts .neverPartOfCompilation ();
11591163 if (cacheInSingleContext || !singleContext ) {
1160- return cachedCallTargets . computeIfAbsent ( key , k -> PythonUtils . getOrCreateCallTarget ( rootNodeFunction . apply ( this )) );
1164+ return getOrCreateCachedCallTarget ( rootNodeFunction , key );
11611165 } else {
11621166 return PythonUtils .getOrCreateCallTarget (rootNodeFunction .apply (this ));
11631167 }
@@ -1167,6 +1171,28 @@ private RootCallTarget createCachedCallTargetUnsafe(Function<PythonLanguage, Roo
11671171 return createCachedCallTargetUnsafe (rootNodeFunction , Arrays .asList (cacheKeys ), cacheInSingleContext );
11681172 }
11691173
1174+ private RootCallTarget getOrCreateCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Object key ) {
1175+ CompilerAsserts .neverPartOfCompilation ();
1176+ if (ImageInfo .inImageRuntimeCode ()) {
1177+ RootCallTarget preinitialized = imageBuildtimeCachedCallTargets .get (key );
1178+ if (preinitialized != null ) {
1179+ return preinitialized ;
1180+ }
1181+ return runtimeCachedCallTargets .computeIfAbsent (key , k -> PythonUtils .getOrCreateCallTarget (rootNodeFunction .apply (this )));
1182+ }
1183+ if (ImageInfo .inImageBuildtimeCode ()) {
1184+ synchronized (imageBuildtimeCachedCallTargets ) {
1185+ RootCallTarget cached = imageBuildtimeCachedCallTargets .get (key );
1186+ if (cached == null ) {
1187+ cached = PythonUtils .getOrCreateCallTarget (rootNodeFunction .apply (this ));
1188+ imageBuildtimeCachedCallTargets .put (key , cached );
1189+ }
1190+ return cached ;
1191+ }
1192+ }
1193+ return runtimeCachedCallTargets .computeIfAbsent (key , k -> PythonUtils .getOrCreateCallTarget (rootNodeFunction .apply (this )));
1194+ }
1195+
11701196 @ Override
11711197 protected void exitContext (PythonContext context , ExitMode exitMode , int exitCode ) {
11721198 if (context .getCApiContext () != null ) {
0 commit comments