40
40
*/
41
41
package com .oracle .graal .python .builtins .objects .cext .capi ;
42
42
43
+ import static com .oracle .graal .python .PythonLanguage .CONTEXT_INSENSITIVE_SINGLETONS ;
44
+ import static com .oracle .graal .python .builtins .objects .cext .capi .PythonNativeWrapper .PythonAbstractObjectNativeWrapper .IMMORTAL_REFCNT ;
43
45
import static com .oracle .graal .python .nodes .SpecialAttributeNames .T___FILE__ ;
44
46
import static com .oracle .graal .python .nodes .SpecialAttributeNames .T___LIBRARY__ ;
45
47
import static com .oracle .graal .python .nodes .StringLiterals .J_LLVM_LANGUAGE ;
72
74
import com .oracle .graal .python .builtins .modules .cext .PythonCextBuiltins .CApiBuiltinExecutable ;
73
75
import com .oracle .graal .python .builtins .modules .cext .PythonCextBuiltins .CApiCallPath ;
74
76
import com .oracle .graal .python .builtins .objects .PNone ;
77
+ import com .oracle .graal .python .builtins .objects .PythonAbstractObject ;
75
78
import com .oracle .graal .python .builtins .objects .capsule .PyCapsule ;
76
79
import com .oracle .graal .python .builtins .objects .cext .capi .CExtNodesFactory .CreateModuleNodeGen ;
80
+ import com .oracle .graal .python .builtins .objects .cext .capi .PythonNativeWrapper .PythonAbstractObjectNativeWrapper ;
77
81
import com .oracle .graal .python .builtins .objects .cext .capi .transitions .CApiTransitions ;
82
+ import com .oracle .graal .python .builtins .objects .cext .capi .transitions .CApiTransitions .HandleContext ;
78
83
import com .oracle .graal .python .builtins .objects .cext .capi .transitions .CApiTransitions .NativeToPythonNode ;
79
- import com .oracle .graal .python .builtins .objects .cext .capi .transitions .CApiTransitions .NativeToPythonStealingNode ;
84
+ import com .oracle .graal .python .builtins .objects .cext .capi .transitions .CApiTransitions .ToPythonWrapperNode ;
80
85
import com .oracle .graal .python .builtins .objects .cext .common .CExtCommonNodes .CheckFunctionResultNode ;
81
86
import com .oracle .graal .python .builtins .objects .cext .common .CExtContext ;
82
87
import com .oracle .graal .python .builtins .objects .cext .common .LoadCExtException .ApiInitException ;
118
123
import com .oracle .truffle .api .TruffleFile ;
119
124
import com .oracle .truffle .api .TruffleLanguage .Env ;
120
125
import com .oracle .truffle .api .TruffleLogger ;
126
+ import com .oracle .truffle .api .TruffleSafepoint ;
121
127
import com .oracle .truffle .api .frame .VirtualFrame ;
122
128
import com .oracle .truffle .api .interop .ArityException ;
123
129
import com .oracle .truffle .api .interop .InteropLibrary ;
127
133
import com .oracle .truffle .api .interop .UnsupportedTypeException ;
128
134
import com .oracle .truffle .api .library .ExportLibrary ;
129
135
import com .oracle .truffle .api .library .ExportMessage ;
136
+ import com .oracle .truffle .api .nodes .ExplodeLoop ;
137
+ import com .oracle .truffle .api .nodes .ExplodeLoop .LoopExplosionKind ;
130
138
import com .oracle .truffle .api .nodes .Node ;
131
139
import com .oracle .truffle .api .source .Source ;
132
140
import com .oracle .truffle .api .source .Source .SourceBuilder ;
@@ -139,6 +147,12 @@ public final class CApiContext extends CExtContext {
139
147
140
148
public static final String LOGGER_CAPI_NAME = "capi" ;
141
149
150
+ /** Same as _PY_NSMALLNEGINTS */
151
+ public static final int PY_NSMALLNEGINTS = 5 ;
152
+
153
+ /** Same as _PY_NSMALLPOSINTS */
154
+ public static final int PY_NSMALLPOSINTS = 257 ;
155
+
142
156
/**
143
157
* NFI source for Python module init functions (i.e. {@code "PyInit_modname"}).
144
158
*/
@@ -162,6 +176,9 @@ public final class CApiContext extends CExtContext {
162
176
/** Container of pointers that have seen to be free'd. */
163
177
private Map <Object , AllocInfo > freedNativeMemory ;
164
178
179
+ /** Native wrappers for context-insensitive singletons like {@link PNone#NONE}. */
180
+ @ CompilationFinal (dimensions = 1 ) private final PythonAbstractObjectNativeWrapper [] singletonNativePtrs ;
181
+
165
182
/**
166
183
* This cache is used to cache native wrappers for frequently used primitives. This is strictly
167
184
* defined to be the range {@code [-5, 256]}. CPython does exactly the same (see
@@ -283,10 +300,22 @@ public CApiContext(PythonContext context, Object llvmLibrary, boolean useNativeB
283
300
CApiContext .nativeSymbolCacheSingleContextUsed = true ;
284
301
}
285
302
303
+ // initialize singleton native wrappers
304
+ singletonNativePtrs = new PythonAbstractObjectNativeWrapper [CONTEXT_INSENSITIVE_SINGLETONS .length ];
305
+ // Other threads must see the nativeWrapper fully initialized once it becomes non-null
306
+ for (int i = 0 ; i < singletonNativePtrs .length ; i ++) {
307
+ assert CApiGuards .isSpecialSingleton (CONTEXT_INSENSITIVE_SINGLETONS [i ]);
308
+ /*
309
+ * Note: this does intentionally not use 'PythonObjectNativeWrapper.wrap' because the
310
+ * wrapper must not be reachable from the Python object since the singletons are shared.
311
+ */
312
+ singletonNativePtrs [i ] = new PythonObjectNativeWrapper (CONTEXT_INSENSITIVE_SINGLETONS [i ]);
313
+ }
314
+
286
315
// initialize primitive native wrapper cache
287
- primitiveNativeWrapperCache = new PrimitiveNativeWrapper [262 ];
316
+ primitiveNativeWrapperCache = new PrimitiveNativeWrapper [PY_NSMALLNEGINTS + PY_NSMALLPOSINTS ];
288
317
for (int i = 0 ; i < primitiveNativeWrapperCache .length ; i ++) {
289
- int value = i - 5 ;
318
+ int value = i - PY_NSMALLNEGINTS ;
290
319
assert CApiGuards .isSmallInteger (value );
291
320
primitiveNativeWrapperCache [i ] = PrimitiveNativeWrapper .createInt (value );
292
321
}
@@ -375,6 +404,46 @@ public void tssDelete(long key) {
375
404
tssStorage .remove (key );
376
405
}
377
406
407
+ @ ExplodeLoop (kind = LoopExplosionKind .FULL_UNROLL_UNTIL_RETURN )
408
+ static int getSingletonNativeWrapperIdx (Object obj ) {
409
+ for (int i = 0 ; i < CONTEXT_INSENSITIVE_SINGLETONS .length ; i ++) {
410
+ if (CONTEXT_INSENSITIVE_SINGLETONS [i ] == obj ) {
411
+ return i ;
412
+ }
413
+ }
414
+ return -1 ;
415
+ }
416
+
417
+ public PythonAbstractObjectNativeWrapper getSingletonNativeWrapper (PythonAbstractObject obj ) {
418
+ int singletonNativePtrIdx = CApiContext .getSingletonNativeWrapperIdx (obj );
419
+ if (singletonNativePtrIdx != -1 ) {
420
+ return singletonNativePtrs [singletonNativePtrIdx ];
421
+ }
422
+ return null ;
423
+ }
424
+
425
+ /**
426
+ * Deallocates all singleton wrappers (in {@link #singletonNativePtrs}) which are immortal and
427
+ * must therefore be explicitly free'd. This method modifies the
428
+ * {@link HandleContext#nativeStubLookup stub lookup table} but runs not guest code.
429
+ */
430
+ private void freeSingletonNativeWrappers (HandleContext handleContext ) {
431
+ CompilerAsserts .neverPartOfCompilation ();
432
+ // TODO(fa): this should not require the GIL (GR-51314)
433
+ assert getContext ().ownsGil ();
434
+ for (int i = 0 ; i < singletonNativePtrs .length ; i ++) {
435
+ PythonAbstractObjectNativeWrapper singletonNativeWrapper = singletonNativePtrs [i ];
436
+ singletonNativePtrs [i ] = null ;
437
+ assert singletonNativeWrapper != null ;
438
+ assert getSingletonNativeWrapperIdx (singletonNativeWrapper .getDelegate ()) != -1 ;
439
+ assert !singletonNativeWrapper .isNative () || singletonNativeWrapper .getRefCount () == IMMORTAL_REFCNT ;
440
+ if (singletonNativeWrapper .ref != null ) {
441
+ CApiTransitions .nativeStubLookupRemove (handleContext , singletonNativeWrapper .ref );
442
+ }
443
+ PyTruffleObjectFree .releaseNativeWrapperUncached (singletonNativeWrapper );
444
+ }
445
+ }
446
+
378
447
public PrimitiveNativeWrapper getCachedPrimitiveNativeWrapper (int i ) {
379
448
assert CApiGuards .isSmallInteger (i );
380
449
PrimitiveNativeWrapper primitiveNativeWrapper = primitiveNativeWrapperCache [i + 5 ];
@@ -396,33 +465,61 @@ Object getOrCreateSmallInts() {
396
465
// TODO(fa): this should not require the GIL (GR-51314)
397
466
assert getContext ().ownsGil ();
398
467
if (nativeSmallIntsArray == null ) {
399
- int nSmallNegativeInts = CConstants ._PY_NSMALLNEGINTS .intValue ();
400
- int nSmallPositiveInts = CConstants ._PY_NSMALLPOSINTS .intValue ();
401
- Object smallInts = CStructAccess .AllocateNode .callocUncached (nSmallNegativeInts + nSmallPositiveInts , CStructAccess .POINTER_SIZE );
402
- for (int i = 0 ; i < nSmallNegativeInts + nSmallPositiveInts ; i ++) {
403
- CStructAccessFactory .WriteObjectNewRefNodeGen .getUncached ().writeArrayElement (smallInts , i , i - nSmallNegativeInts );
468
+ assert CConstants ._PY_NSMALLNEGINTS .intValue () == PY_NSMALLNEGINTS ;
469
+ assert CConstants ._PY_NSMALLPOSINTS .intValue () == PY_NSMALLPOSINTS ;
470
+ Object smallInts = CStructAccess .AllocateNode .callocUncached (PY_NSMALLNEGINTS + PY_NSMALLPOSINTS , CStructAccess .POINTER_SIZE );
471
+ for (int i = 0 ; i < PY_NSMALLNEGINTS + PY_NSMALLPOSINTS ; i ++) {
472
+ CStructAccessFactory .WriteObjectNewRefNodeGen .getUncached ().writeArrayElement (smallInts , i , i - PY_NSMALLNEGINTS );
404
473
}
405
474
nativeSmallIntsArray = smallInts ;
406
475
}
407
476
return nativeSmallIntsArray ;
408
477
}
409
478
410
- private void freeSmallInts () {
479
+ /**
480
+ * Deallocates the native small int array (pointer {@link #nativeSmallIntsArray}) and all
481
+ * wrappers of the small ints (in {@link #primitiveNativeWrapperCache}) which are immortal and
482
+ * must therefore be explicitly free'd. This method modifies the
483
+ * {@link HandleContext#nativeStubLookup stub lookup table} but runs not guest code.
484
+ */
485
+ private void freeSmallInts (HandleContext handleContext ) {
411
486
CompilerAsserts .neverPartOfCompilation ();
412
487
// TODO(fa): this should not require the GIL (GR-51314)
413
488
assert getContext ().ownsGil ();
414
489
if (nativeSmallIntsArray != null ) {
415
- int nSmallNegativeInts = CConstants ._PY_NSMALLNEGINTS .intValue ();
416
- int nSmallPositiveInts = CConstants ._PY_NSMALLPOSINTS .intValue ();
417
- for (int i = 0 ; i < nSmallNegativeInts + nSmallPositiveInts ; i ++) {
418
- Object elementPtr = ReadPointerNode .getUncached ().readArrayElement (nativeSmallIntsArray , i );
419
- // again, take ownership
420
- Object element = NativeToPythonStealingNode .executeUncached (elementPtr );
421
- assert element instanceof Number number && number .intValue () == i - nSmallNegativeInts ;
422
- }
490
+ assert verifyNativeSmallInts ();
491
+ // free the native array used to store the stub pointers of the small int wrappers
423
492
FreeNode .executeUncached (nativeSmallIntsArray );
424
493
nativeSmallIntsArray = null ;
425
494
}
495
+ for (PrimitiveNativeWrapper wrapper : primitiveNativeWrapperCache ) {
496
+ assert wrapper .isIntLike () && CApiGuards .isSmallLong (wrapper .getLong ());
497
+ assert !wrapper .isNative () || wrapper .getRefCount () == IMMORTAL_REFCNT ;
498
+ if (wrapper .ref != null ) {
499
+ CApiTransitions .nativeStubLookupRemove (handleContext , wrapper .ref );
500
+ }
501
+ PyTruffleObjectFree .releaseNativeWrapperUncached (wrapper );
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Verifies integrity of the pointers stored in the native small int array. Each pointer must
507
+ * denote the according small int wrapper. The objects are expected to be immortal.
508
+ */
509
+ private boolean verifyNativeSmallInts () {
510
+ // TODO(fa): this should not require the GIL (GR-51314)
511
+ assert getContext ().ownsGil ();
512
+ for (int i = 0 ; i < PY_NSMALLNEGINTS + PY_NSMALLPOSINTS ; i ++) {
513
+ Object elementPtr = ReadPointerNode .getUncached ().readArrayElement (nativeSmallIntsArray , i );
514
+ PythonNativeWrapper wrapper = ToPythonWrapperNode .executeUncached (elementPtr , false );
515
+ if (wrapper != primitiveNativeWrapperCache [i ]) {
516
+ return false ;
517
+ }
518
+ if (primitiveNativeWrapperCache [i ].isNative () && primitiveNativeWrapperCache [i ].getRefCount () != IMMORTAL_REFCNT ) {
519
+ return false ;
520
+ }
521
+ }
522
+ return true ;
426
523
}
427
524
428
525
public Object getModuleByIndex (int i ) {
@@ -798,15 +895,65 @@ private void addNativeFinalizer(Env env, Object resetFunctionPointerArray) {
798
895
}
799
896
}
800
897
801
- @ TruffleBoundary
898
+ /**
899
+ * This method is called to exit the context assuming a
900
+ * {@link com.oracle.truffle.api.TruffleLanguage.ExitMode#NATURAL natural exit}. This means, it
901
+ * is allowed to run guest code. Hence, we deallocate any reachable native object here since
902
+ * they may have custom {@code tp_dealloc} functions.
903
+ */
802
904
@ SuppressWarnings ("try" )
803
- public void finalizeCapi () {
804
- // TODO(fa): remove GIL acquisition (GR-51314)
805
- try (GilNode .UncachedAcquire gil = GilNode .uncachedAcquire ()) {
806
- freeSmallInts ();
905
+ public void exitCApiContext () {
906
+ CompilerAsserts .neverPartOfCompilation ();
907
+ /*
908
+ * Polling the native reference queue is the only task we can do here because deallocating
909
+ * objects may run arbitrary guest code that can again call into the interpreter.
910
+ */
911
+ CApiTransitions .pollReferenceQueue ();
912
+ /*
913
+ * Deallocating native storages and objects may run arbitrary guest code. So, we need to
914
+ * ensure that the GIL is held.
915
+ */
916
+ try (GilNode .UncachedAcquire ignored = GilNode .uncachedAcquire ()) {
917
+ CApiTransitions .deallocateNativeWeakRefs (getContext ());
807
918
}
808
- if (pyDateTimeCAPICapsule != null ) {
809
- PyDateTimeCAPIWrapper .destroyWrapper (pyDateTimeCAPICapsule );
919
+ }
920
+
921
+ @ SuppressWarnings ("try" )
922
+ public void finalizeCApi () {
923
+ CompilerAsserts .neverPartOfCompilation ();
924
+ HandleContext handleContext = getContext ().nativeContext ;
925
+ /*
926
+ * Disable reference queue polling because during finalization, we will free any known
927
+ * allocated resources (e.g. native object stubs). Calling
928
+ * 'CApiTransitions.pollReferenceQueue' could then lead to a double-free.
929
+ */
930
+ CApiTransitions .disableReferenceQueuePolling (handleContext );
931
+
932
+ TruffleSafepoint sp = TruffleSafepoint .getCurrent ();
933
+ boolean prev = sp .setAllowActions (false );
934
+ try {
935
+ // TODO(fa): remove GIL acquisition (GR-51314)
936
+ try (GilNode .UncachedAcquire ignored = GilNode .uncachedAcquire ()) {
937
+ freeSmallInts (handleContext );
938
+ freeSingletonNativeWrappers (handleContext );
939
+ /*
940
+ * Clear all remaining native object stubs. This must be done after the small int
941
+ * and the singleton wrappers were cleared because they might also end up in the
942
+ * lookup table and may otherwise be double-freed.
943
+ */
944
+ CApiTransitions .freeNativeObjectStubs (handleContext );
945
+ CApiTransitions .freeClassReplacements (handleContext );
946
+ CApiTransitions .freeNativeStorages (handleContext );
947
+ }
948
+ if (pyDateTimeCAPICapsule != null ) {
949
+ PyDateTimeCAPIWrapper .destroyWrapper (pyDateTimeCAPICapsule );
950
+ }
951
+ // free all allocated PyMethodDef structures
952
+ for (Object pyMethodDefPointer : methodDefinitions .values ()) {
953
+ PyMethodDefHelper .free (pyMethodDefPointer );
954
+ }
955
+ } finally {
956
+ sp .setAllowActions (prev );
810
957
}
811
958
if (nativeFinalizerShutdownHook != null ) {
812
959
try {
@@ -817,10 +964,6 @@ public void finalizeCapi() {
817
964
}
818
965
}
819
966
pyCFunctionWrappers .clear ();
820
- // free all allocated PyMethodDef structures
821
- for (Object pyMethodDefPointer : methodDefinitions .values ()) {
822
- PyMethodDefHelper .free (pyMethodDefPointer );
823
- }
824
967
/*
825
968
* If the static symbol cache is not null, then it is guaranteed that this context instance
826
969
* was the exclusive user of it. We can now reset the state such that other contexts created
0 commit comments