Skip to content

Commit c5c5da6

Browse files
committed
Free native memory of native heap types
1 parent 81d6738 commit c5c5da6

File tree

5 files changed

+56
-33
lines changed

5 files changed

+56
-33
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PThreadState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public final class PThreadState extends PythonStructNativeWrapper {
6969
private PThreadState(PythonThreadState threadState) {
7070
super(threadState, true);
7171
// 'registerReplacement' will set the native pointer if not running LLVM managed mode.
72-
replacement = registerReplacement(allocateCLayout(threadState), InteropLibrary.getUncached());
72+
replacement = registerReplacement(allocateCLayout(threadState), false, InteropLibrary.getUncached());
7373
}
7474

7575
public static Object getThreadState(PythonLanguage language, PythonContext context) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMemoryViewWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ private static Object allocate(PMemoryView object) {
165165
public Object getReplacement(InteropLibrary lib) {
166166
if (replacement == null) {
167167
Object pointerObject = allocate((PMemoryView) getDelegate());
168-
replacement = registerReplacement(pointerObject, lib);
168+
replacement = registerReplacement(pointerObject, false, lib);
169169
}
170170
return replacement;
171171
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PythonClassNativeWrapper.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import com.oracle.graal.python.builtins.objects.cext.common.CArrayWrappers.CStringWrapper;
4545
import com.oracle.graal.python.builtins.objects.cext.structs.CFields;
4646
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
47-
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccessFactory;
47+
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.AllocateNode;
4848
import com.oracle.graal.python.builtins.objects.cext.structs.CStructs;
4949
import com.oracle.graal.python.builtins.objects.type.PythonClass;
5050
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
@@ -55,7 +55,6 @@
5555
import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.SetItemSizeNodeGen;
5656
import com.oracle.graal.python.nodes.HiddenAttr;
5757
import com.oracle.graal.python.nodes.PGuards;
58-
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
5958
import com.oracle.graal.python.util.PythonUtils;
6059
import com.oracle.truffle.api.CompilerAsserts;
6160
import com.oracle.truffle.api.CompilerDirectives;
@@ -111,7 +110,6 @@ public static void wrapNative(PythonManagedClass clazz, TruffleString name, Obje
111110

112111
CStructAccess.ReadI64Node readI64 = CStructAccess.ReadI64Node.getUncached();
113112
CStructAccess.ReadPointerNode readPointer = CStructAccess.ReadPointerNode.getUncached();
114-
WriteAttributeToObjectNode writeAttr = WriteAttributeToObjectNode.getUncached();
115113
InteropLibrary lib = InteropLibrary.getUncached();
116114

117115
// some values are retained from the native representation
@@ -158,7 +156,7 @@ public static void wrapNative(PythonManagedClass clazz, TruffleString name, Obje
158156
* 'getReplacement' for more explanation).
159157
*/
160158
wrapper.replacement = pointer;
161-
wrapper.registerReplacement(pointer, lib);
159+
wrapper.registerReplacement(pointer, false, lib);
162160
}
163161

164162
public static void initNative(PythonManagedClass clazz, Object pointer) {
@@ -190,8 +188,8 @@ public Object getReplacement(InteropLibrary lib) {
190188
*/
191189
PythonManagedClass clazz = (PythonManagedClass) getDelegate();
192190
boolean heaptype = (GetTypeFlagsNode.executeUncached(clazz) & TypeFlags.HEAPTYPE) != 0;
193-
Object pointerObject = CStructAccessFactory.AllocateNodeGen.getUncached().alloc(heaptype ? CStructs.PyHeapTypeObject : CStructs.PyTypeObject);
194-
replacement = registerReplacement(pointerObject, lib);
191+
Object pointerObject = AllocateNode.allocUncached(heaptype ? CStructs.PyHeapTypeObject : CStructs.PyTypeObject);
192+
replacement = registerReplacement(pointerObject, true, lib);
195193

196194
ToNativeTypeNode.initializeType(this, pointerObject, heaptype);
197195
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PythonNativeWrapper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,19 @@ public Object getReplacement(@SuppressWarnings("unused") InteropLibrary lib) {
139139
}
140140

141141
@TruffleBoundary
142-
public final Object registerReplacement(Object pointer, InteropLibrary lib) {
142+
public final Object registerReplacement(Object pointer, boolean freeAtCollection, InteropLibrary lib) {
143143
LOGGER.finest(() -> PythonUtils.formatJString("assigning %s with %s", getDelegate(), pointer));
144144
Object result;
145145
if (pointer instanceof Long lptr) {
146146
// need to convert to actual pointer
147147
result = PCallCapiFunction.callUncached(NativeCAPISymbol.FUN_CONVERT_POINTER, lptr);
148-
CApiTransitions.createReference(this, lptr);
148+
CApiTransitions.createReference(this, lptr, freeAtCollection);
149149
} else {
150150
result = pointer;
151151
if (lib.isPointer(pointer)) {
152152
assert CApiContext.isPointerObject(pointer);
153153
try {
154-
CApiTransitions.createReference(this, lib.asPointer(pointer));
154+
CApiTransitions.createReference(this, lib.asPointer(pointer), freeAtCollection);
155155
} catch (UnsupportedMessageException e) {
156156
throw CompilerDirectives.shouldNotReachHere(e);
157157
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -207,29 +207,40 @@ public static final class PythonObjectReference extends IdReference<PythonNative
207207
* The index in the lookup table, where this reference is stored. This duplicates the native
208208
* field {@link CFields#GraalPyObject__handle_table_index} in order to save reading the
209209
* native field if we already have the reference object. The value may be {@code -1} in
210-
* which case it means that the reference's referent is a managed object and
211-
* {@link #pointer} points into the handle space.
210+
* which case it means that {@link #pointer} is not a tagged pointer but a pointer to some
211+
* other off-heap memory (e.g. {@code PyTypeObject} or other Python structs).
212212
*/
213213
private final int handleTableIndex;
214214

215-
private PythonObjectReference(HandleContext handleContext, PythonNativeWrapper referent, boolean strong, long pointer, int handleTableIndex) {
215+
/**
216+
* Indicates if the native memory {@link #pointer} should be freed (using {@link FreeNode})
217+
* if this reference was enqueued because the referent was collected. For example, this will
218+
* be {@code true} if the referent is
219+
* {@link com.oracle.graal.python.builtins.objects.cext.capi.PythonClassNativeWrapper} and
220+
* the class native replacement was allocated on the heap (usually a heap type). It will be
221+
* {@code false} if the class wraps a static type.
222+
*/
223+
private final boolean freeAtCollection;
224+
225+
private PythonObjectReference(HandleContext handleContext, PythonNativeWrapper referent, boolean strong, long pointer, int handleTableIndex, boolean freeAtCollection) {
216226
super(handleContext, referent);
217227
this.pointer = pointer;
218228
this.strongReference = strong ? referent : null;
219229
referent.ref = this;
220230
this.handleTableIndex = handleTableIndex;
231+
this.freeAtCollection = freeAtCollection;
221232
if (LOGGER.isLoggable(Level.FINE)) {
222233
LOGGER.fine(PythonUtils.formatJString("new %s", toString()));
223234
}
224235
}
225236

226237
static PythonObjectReference create(HandleContext handleContext, PythonAbstractObjectNativeWrapper referent, boolean strong, long pointer, int idx) {
227238
assert HandlePointerConverter.pointsToPyHandleSpace(pointer);
228-
return new PythonObjectReference(handleContext, referent, strong, pointer, idx);
239+
return new PythonObjectReference(handleContext, referent, strong, pointer, idx, true);
229240
}
230241

231-
static PythonObjectReference create(HandleContext handleContext, PythonNativeWrapper referent, long pointer) {
232-
return new PythonObjectReference(handleContext, referent, true, pointer, -1);
242+
static PythonObjectReference create(HandleContext handleContext, PythonNativeWrapper referent, long pointer, boolean freeAtCollection) {
243+
return new PythonObjectReference(handleContext, referent, true, pointer, -1, freeAtCollection);
233244
}
234245

235246
public boolean isStrongReference() {
@@ -244,6 +255,10 @@ public int getHandleTableIndex() {
244255
return handleTableIndex;
245256
}
246257

258+
public boolean isFreeAtCollection() {
259+
return freeAtCollection;
260+
}
261+
247262
@Override
248263
@TruffleBoundary
249264
public String toString() {
@@ -390,6 +405,9 @@ public static void pollReferenceQueue() {
390405
} else {
391406
assert nativeLookupGet(context, reference.pointer) != null : Long.toHexString(reference.pointer);
392407
nativeLookupRemove(context, reference.pointer);
408+
if (reference.freeAtCollection) {
409+
freeNativeStruct(reference);
410+
}
393411
}
394412
} else if (entry instanceof NativeObjectReference reference) {
395413
nativeLookupRemove(context, reference.pointer);
@@ -454,21 +472,16 @@ public static void freeClassReplacements(HandleContext handleContext) {
454472
assert PythonContext.get(null).ownsGil();
455473
handleContext.nativeLookup.forEach((l, ref) -> {
456474
if (ref instanceof PythonObjectReference reference) {
457-
/*
458-
* If a PythonObjectReference is in this table, it either refers to an object that
459-
* is backed by static native memory (e.g. static PyTypeObject), or it refers to
460-
* some non-object structure (e.g. PyThreadState). In any case, the objects are
461-
* immutable and so the references are expected to be strong.
462-
*/
463-
assert reference.strongReference != null;
475+
// We don't expect references to wrappers that would have a native object stub.
464476
assert reference.handleTableIndex == -1;
465477
/*
466-
* TODO(fa): For classes, distinguish between static and heap memory.
467-
*
468-
* The ref may denote class wrappers where some of them are backed by static native
469-
* memory and some of them were allocated in heap. There is currently no reliable
470-
* way to distinguish them and so we leak the heap types.
478+
* The ref may denote: (a) class wrappers, where some of them are backed by static
479+
* native memory and some of them were allocated in heap, and (b) struct wrappers,
480+
* which may be freed manually in a separate step.
471481
*/
482+
if (reference.freeAtCollection) {
483+
freeNativeStruct(reference);
484+
}
472485
}
473486
});
474487
handleContext.nativeLookup.clear();
@@ -479,7 +492,7 @@ public static void disableReferenceQueuePolling(HandleContext handleContext) {
479492
}
480493

481494
private static void freeNativeStub(long stubPointer) {
482-
LOGGER.fine(() -> PythonUtils.formatJString("freeing native object stub 0x%x", stubPointer));
495+
LOGGER.fine(() -> PythonUtils.formatJString("releasing native object stub 0x%x", stubPointer));
483496
FreeNode.executeUncached(stubPointer);
484497
}
485498

@@ -488,6 +501,13 @@ private static void freeNativeStub(PythonObjectReference ref) {
488501
freeNativeStub(HandlePointerConverter.pointerToStub(ref.pointer));
489502
}
490503

504+
private static void freeNativeStruct(PythonObjectReference ref) {
505+
assert ref.handleTableIndex == -1;
506+
assert ref.freeAtCollection;
507+
LOGGER.fine(() -> PythonUtils.formatJString("releasing %s", ref.toString()));
508+
FreeNode.executeUncached(ref.pointer);
509+
}
510+
491511
private static void freeNativeStorage(NativeStorageReference ref) {
492512
LOGGER.fine(() -> PythonUtils.formatJString("releasing %s", ref.toString()));
493513
FreeNode.executeUncached(ref.ptr);
@@ -617,7 +637,12 @@ public static IdReference<?> nativeLookupGet(HandleContext context, long pointer
617637
}
618638

619639
@TruffleBoundary
620-
public static IdReference<?> nativeLookupPut(HandleContext context, long pointer, IdReference<?> value) {
640+
public static IdReference<?> nativeLookupPut(HandleContext context, long pointer, NativeObjectReference value) {
641+
return context.nativeLookup.put(pointer, value);
642+
}
643+
644+
@TruffleBoundary
645+
public static IdReference<?> nativeLookupPut(HandleContext context, long pointer, PythonObjectReference value) {
621646
return context.nativeLookup.put(pointer, value);
622647
}
623648

@@ -814,7 +839,7 @@ static long doGeneric(Node inliningTarget, PythonAbstractObjectNativeWrapper wra
814839
*/
815840
@TruffleBoundary
816841
@SuppressWarnings("try")
817-
public static void createReference(PythonNativeWrapper obj, long ptr) {
842+
public static void createReference(PythonNativeWrapper obj, long ptr, boolean freeAtCollection) {
818843
try (GilNode.UncachedAcquire ignored = GilNode.uncachedAcquire()) {
819844
/*
820845
* The first test if '!obj.isNative()' in the caller is done on a fast-path but not
@@ -825,7 +850,7 @@ public static void createReference(PythonNativeWrapper obj, long ptr) {
825850
obj.setNativePointer(ptr);
826851
pollReferenceQueue();
827852
HandleContext context = getContext();
828-
nativeLookupPut(context, ptr, PythonObjectReference.create(context, obj, ptr));
853+
nativeLookupPut(context, ptr, PythonObjectReference.create(context, obj, ptr, freeAtCollection));
829854
}
830855
}
831856
}

0 commit comments

Comments
 (0)