37
37
import java .io .IOException ;
38
38
import java .io .InputStream ;
39
39
import java .lang .invoke .VarHandle ;
40
+ import java .lang .ref .WeakReference ;
40
41
import java .nio .charset .StandardCharsets ;
41
42
import java .nio .file .InvalidPathException ;
42
43
import java .util .ArrayList ;
70
71
import com .oracle .graal .python .builtins .objects .function .PArguments ;
71
72
import com .oracle .graal .python .builtins .objects .module .PythonModule ;
72
73
import com .oracle .graal .python .builtins .objects .object .PythonObject ;
74
+ import com .oracle .graal .python .builtins .objects .tuple .StructSequence ;
75
+ import com .oracle .graal .python .builtins .objects .tuple .StructSequence .BuiltinTypeDescriptor ;
76
+ import com .oracle .graal .python .builtins .objects .tuple .StructSequence .Descriptor ;
77
+ import com .oracle .graal .python .builtins .objects .tuple .StructSequence .DescriptorCallTargets ;
73
78
import com .oracle .graal .python .builtins .objects .type .MroShape ;
74
79
import com .oracle .graal .python .builtins .objects .type .PythonAbstractClass ;
75
80
import com .oracle .graal .python .builtins .objects .type .PythonManagedClass ;
@@ -333,6 +338,46 @@ public boolean isSingleContext() {
333
338
334
339
@ CompilationFinal (dimensions = 1 ) private final RootCallTarget [] builtinSlotsCallTargets ;
335
340
341
+ /**
342
+ * Weak hash map of call targets for builtin functions associated with named tuples generated at
343
+ * runtime from C extensions. We hold the cached call targets also weakly, because otherwise we
344
+ * would have a cycle from the value (call targets reference builtin nodes which wrap the
345
+ * descriptor) to the key. The key should be GC'ed when the corresponding generated named tuple
346
+ * class is GC'ed.
347
+ */
348
+ private final WeakHashMap <StructSequence .Descriptor , WeakReference <StructSequence .DescriptorCallTargets >> structSequenceTargets = new WeakHashMap <>();
349
+
350
+ /**
351
+ * The same as {@link #structSequenceTargets}, but for builtin named tuples. There is a bounded
352
+ * statically known number of builtin named tuples.
353
+ */
354
+ private final ConcurrentHashMap <StructSequence .Descriptor , StructSequence .DescriptorCallTargets > structSequenceBuiltinTargets = new ConcurrentHashMap <>();
355
+
356
+ public StructSequence .DescriptorCallTargets getOrCreateStructSequenceCallTargets (StructSequence .Descriptor descriptor ,
357
+ Function <StructSequence .Descriptor , StructSequence .DescriptorCallTargets > factory ) {
358
+ if (singleContext ) {
359
+ return factory .apply (descriptor );
360
+ }
361
+ if (descriptor instanceof BuiltinTypeDescriptor builtinDescriptor ) {
362
+ // There must be finite set of objects initialized at build time, no need for a weak map
363
+ assert !ImageInfo .inImageCode () || builtinDescriptor .wasInitializedAtBuildTime ();
364
+ return structSequenceBuiltinTargets .computeIfAbsent (builtinDescriptor , factory );
365
+ }
366
+ return getOrCreateStructSeqNonBuiltinTargets (descriptor , factory );
367
+ }
368
+
369
+ private DescriptorCallTargets getOrCreateStructSeqNonBuiltinTargets (Descriptor descriptor , Function <Descriptor , DescriptorCallTargets > factory ) {
370
+ synchronized (structSequenceTargets ) {
371
+ WeakReference <DescriptorCallTargets > weakResult = structSequenceTargets .computeIfAbsent (descriptor , d -> new WeakReference <>(factory .apply (d )));
372
+ DescriptorCallTargets result = weakResult .get ();
373
+ if (result == null ) {
374
+ result = factory .apply (descriptor );
375
+ structSequenceTargets .put (descriptor , new WeakReference <>(result ));
376
+ }
377
+ return result ;
378
+ }
379
+ }
380
+
336
381
/**
337
382
* We cannot initialize call targets in language ctor and the next suitable hook is context
338
383
* initialization, but that is called multiple times. We use this flag to run the language
@@ -410,6 +455,9 @@ public MroShape getMroShapeRoot() {
410
455
protected void finalizeContext (PythonContext context ) {
411
456
context .finalizeContext ();
412
457
super .finalizeContext (context );
458
+ // trigger cleanup of stale entries in weak hash maps
459
+ structSequenceTargets .size ();
460
+ indirectCallDataMap .size ();
413
461
}
414
462
415
463
@ Override
@@ -1000,9 +1048,87 @@ public void setBuiltinSlotCallTarget(int index, RootCallTarget callTarget) {
1000
1048
}
1001
1049
1002
1050
/**
1003
- * Cache call targets that are created for every new context, based on a single key.
1051
+ * Caches call target that wraps a node that is not parametrized, i.e., has only a parameterless
1052
+ * ctor and all its instances implement the same logic. Parametrized nodes must include the
1053
+ * parameters that alter their behavior as part of the cache key.
1054
+ */
1055
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends Node > key ) {
1056
+ // It's complicated with RootNodes, but regular nodes should have only parameterless ctor to
1057
+ // be appropriate keys for the cache
1058
+ assert RootNode .class .isAssignableFrom (key ) || key .getConstructors ().length <= 1 ;
1059
+ assert RootNode .class .isAssignableFrom (key ) || key .getConstructors ().length == 0 || key .getConstructors ()[0 ].getParameterCount () == 0 ;
1060
+ return createCachedCallTargetUnsafe (rootNodeFunction , key );
1061
+ }
1062
+
1063
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Enum <?> key ) {
1064
+ return createCachedCallTargetUnsafe (rootNodeFunction , key );
1065
+ }
1066
+
1067
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends Node > nodeClass , String key ) {
1068
+ // for builtins: name is needed to distinguish builtins that share the same underlying node
1069
+ // in general: a String may be parameter of the node wrapped in the root node or the root
1070
+ // node itself, there must be finite number of strings that can appear here (i.e., must not
1071
+ // be dynamically generated unless their number is bounded).
1072
+ return createCachedCallTargetUnsafe (rootNodeFunction , nodeClass , key );
1073
+ }
1074
+
1075
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends Node > nodeClass , TruffleString key ) {
1076
+ // See the String overload
1077
+ return createCachedCallTargetUnsafe (rootNodeFunction , nodeClass , key );
1078
+ }
1079
+
1080
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends Node > nodeClass , int key ) {
1081
+ return createCachedCallTargetUnsafe (rootNodeFunction , nodeClass , key );
1082
+ }
1083
+
1084
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends Node > nodeClass1 , Class <?> nodeClass2 , String name ) {
1085
+ // for slot wrappers: the root node may be wrapping a helper wrapper node implementing the
1086
+ // wrapper logic and the bare slot node itself
1087
+ return createCachedCallTargetUnsafe (rootNodeFunction , nodeClass1 , nodeClass2 , name );
1088
+ }
1089
+
1090
+ /**
1091
+ * Caches call targets for external C functions created by extensions at runtime.
1092
+ * <p>
1093
+ * For the time being, we assume finite/limited number of extensions and their external
1094
+ * functions. This may hold onto call targets created by one extension used in a context that
1095
+ * was closed in the meanwhile and no other context ever loads the extension.
1004
1096
*/
1005
- public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Object key ) {
1097
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <? extends RootNode > klass , Enum <?> signature , TruffleString name ,
1098
+ boolean doArgumentAndResultConversion ) {
1099
+ return createCachedCallTargetUnsafe (rootNodeFunction , klass , signature , name , doArgumentAndResultConversion );
1100
+ }
1101
+
1102
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Enum <?> signature , TruffleString name ,
1103
+ boolean doArgumentAndResultConversion ) {
1104
+ return createCachedCallTargetUnsafe (rootNodeFunction , signature , name , doArgumentAndResultConversion );
1105
+ }
1106
+
1107
+ public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Enum <?> signature , TruffleString name ) {
1108
+ return createCachedCallTargetUnsafe (rootNodeFunction , signature , name );
1109
+ }
1110
+
1111
+ public RootCallTarget createStructSeqIndexedMemberAccessCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , int memberIndex ) {
1112
+ return createCachedCallTargetUnsafe (rootNodeFunction , StructSequence .class , memberIndex );
1113
+ }
1114
+
1115
+ public RootCallTarget createCachedPropAccessCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Class <?> nodeClass , String name , int type , int offset ) {
1116
+ // For the time being, we assume finite/limited number of cext/hpy types members, their
1117
+ // types and offsets
1118
+ return createCachedCallTargetUnsafe (rootNodeFunction , nodeClass , name , type , offset );
1119
+ }
1120
+
1121
+ /**
1122
+ * Keys in any caches held by {@link PythonLanguage} must be context independent objects and
1123
+ * there must be either finite number of their instances, or if the key is a context independent
1124
+ * mirror of some runtime data structure, it must be cached weakly. This call targets cache is
1125
+ * strong.
1126
+ * <p>
1127
+ * To avoid memory leaks, all key types must be known to have finite number of possible
1128
+ * instances. Public methods for adding to the cache must take concrete key type(s) so that all
1129
+ * possible cache keys are explicit and documented.
1130
+ */
1131
+ private RootCallTarget createCachedCallTargetUnsafe (Function <PythonLanguage , RootNode > rootNodeFunction , Object key ) {
1006
1132
CompilerAsserts .neverPartOfCompilation ();
1007
1133
if (!singleContext ) {
1008
1134
return cachedCallTargets .computeIfAbsent (key , k -> PythonUtils .getOrCreateCallTarget (rootNodeFunction .apply (this )));
@@ -1011,11 +1137,8 @@ public RootCallTarget createCachedCallTarget(Function<PythonLanguage, RootNode>
1011
1137
}
1012
1138
}
1013
1139
1014
- /**
1015
- * Cache call targets that are created for every new context, based on a list of keys.
1016
- */
1017
- public RootCallTarget createCachedCallTarget (Function <PythonLanguage , RootNode > rootNodeFunction , Object ... cacheKeys ) {
1018
- return createCachedCallTarget (rootNodeFunction , Arrays .asList (cacheKeys ));
1140
+ private RootCallTarget createCachedCallTargetUnsafe (Function <PythonLanguage , RootNode > rootNodeFunction , Object ... cacheKeys ) {
1141
+ return createCachedCallTargetUnsafe (rootNodeFunction , Arrays .asList (cacheKeys ));
1019
1142
}
1020
1143
1021
1144
public void registerBuiltinDescriptorCallTarget (BuiltinMethodDescriptor descriptor , RootCallTarget callTarget ) {
0 commit comments