Repository navigation
[memory-ownership] Honour ownership attributes when taking and releasing memory #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,20 +29,24 @@ class CPPInstance { | |||||||||
| public: | ||||||||||
| enum EFlags { | ||||||||||
| kDefault = 0x0000, | ||||||||||
| kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) | ||||||||||
| kIsOwner = 0x0002, // Python instance owns C++ object/memory | ||||||||||
| kIsExtended = 0x0004, // has extended data | ||||||||||
| kIsValue = 0x0008, // was created from a by-value return | ||||||||||
| kIsReference = 0x0010, // represents one indirection | ||||||||||
| kIsArray = 0x0020, // represents an array of objects | ||||||||||
| kIsSmartPtr = 0x0040, // is or embeds a smart pointer | ||||||||||
| kIsPtrPtr = 0x0080, // represents two indirections | ||||||||||
| kIsRValue = 0x0100, // can be used as an r-value | ||||||||||
| kIsLValue = 0x0200, // can be used as an l-value | ||||||||||
| kNoMemReg = 0x0400, // do not register with memory regulator | ||||||||||
| kIsRegulated = 0x0800, // is registered with memory regulator | ||||||||||
| kIsActual = 0x1000, // has been downcasted to actual type | ||||||||||
| kHasLifeLine = 0x2000, // has a life line set | ||||||||||
| kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) | ||||||||||
| kIsOwner = 0x0002, // Python instance owns C++ object/memory | ||||||||||
| kIsExtended = 0x0004, // has extended data | ||||||||||
| kIsValue = 0x0008, // was created from a by-value return | ||||||||||
| kIsReference = 0x0010, // represents one indirection | ||||||||||
| kIsArray = 0x0020, // represents an array of objects | ||||||||||
| kIsSmartPtr = 0x0040, // is or embeds a smart pointer | ||||||||||
| kIsPtrPtr = 0x0080, // represents two indirections | ||||||||||
| kIsRValue = 0x0100, // can be used as an r-value | ||||||||||
| kIsLValue = 0x0200, // can be used as an l-value | ||||||||||
| kNoMemReg = 0x0400, // do not register with memory regulator | ||||||||||
| kIsRegulated = 0x0800, // is registered with memory regulator | ||||||||||
| kIsActual = 0x1000, // has been downcasted to actual type | ||||||||||
| kHasLifeLine = 0x2000, // has a life line set | ||||||||||
| kIsArrayAlloc = 0x4000, // represents a heap allocated array of objects | ||||||||||
| kIsNoConstruct = | ||||||||||
| 0x8000, // represents constructor is not called in the allocation | ||||||||||
|
Comment on lines
+47
to
+48
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not have this in a negative way. It becomes difficult to read.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since before allocation details the assumption was constructor was run for each instance, and we are adding a kIsNoConstruct to reflect the opposite possibility. If I have added kIsConstruct I would have to revert each instance creation mechanism and probably more things. Also git diff is still large for flags because of clang-format |
||||||||||
| kIsMalloc = 0x10000, // is allocated with malloc | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| public: // public, as the python C-API works with C structs | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -142,14 +142,23 @@ static inline void ResetCallState(CPPInstance* descr_self, | |||||||||
|
|
||||||||||
| // helper to factor out return logic of mp_call / mp_vectorcall | ||||||||||
| static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, | ||||||||||
| PyObject* result) { | ||||||||||
| PyObject* result, PyCallable* method, | ||||||||||
| PyObject* const* args, size_t nargsf, | ||||||||||
| PyObject* kwds) { | ||||||||||
| // special case for python exceptions, propagated through C++ layer | ||||||||||
|
Comment on lines
+145
to
148
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh my bad, for CPPMethod objects, call function calls ProcessArgs and it changes the self, but changes the duplicate but I thought we should compute it before Call()
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handled |
||||||||||
| if (result) { | ||||||||||
| CPPInstance* cppres = | ||||||||||
| (CPPInstance*)(CPPInstance_Check(result) ? result : nullptr); | ||||||||||
|
|
||||||||||
| interop::AllocType AT = method->GetAllocBehaviour(); | ||||||||||
| // the overloads of a group share fMethodInfo, so the answer of this one | ||||||||||
| // stays in a local instead of going into the shared flags | ||||||||||
| const bool allocates = AT != interop::AllocType::None && | ||||||||||
| AT != interop::AllocType::Null && | ||||||||||
| AT != interop::AllocType::Unknown; | ||||||||||
|
|
||||||||||
| // if this method creates new objects, always take ownership | ||||||||||
| if (IsCreator(pymeth->fMethodInfo->fFlags)) { | ||||||||||
| if (allocates || IsCreator(pymeth->fMethodInfo->fFlags)) { | ||||||||||
|
|
||||||||||
| // either be a constructor with a fresh object proxy self ... | ||||||||||
| if (IsConstructor(pymeth->fMethodInfo->fFlags)) { | ||||||||||
|
|
@@ -158,9 +167,76 @@ static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, | |||||||||
| } | ||||||||||
|
|
||||||||||
| // ... or be a regular method with an object proxy return value | ||||||||||
| else if (cppres) | ||||||||||
| else if (cppres) { | ||||||||||
| cppres->PythonOwns(); | ||||||||||
| // After giving ownership, set proper flags to indicate allocation | ||||||||||
| // method/func | ||||||||||
| switch (AT) { | ||||||||||
| case interop::AllocType::Malloc: | ||||||||||
| cppres->fFlags |= CPPInstance::kIsMalloc; | ||||||||||
| break; | ||||||||||
| case interop::AllocType::NewArr: | ||||||||||
| cppres->fFlags |= CPPInstance::kIsArrayAlloc; | ||||||||||
| break; | ||||||||||
| case interop::AllocType::OperatorNew: | ||||||||||
| cppres->fFlags |= CPPInstance::kIsNoConstruct; | ||||||||||
| break; | ||||||||||
| case interop::AllocType::OperatorNewArr: | ||||||||||
| cppres->fFlags |= CPPInstance::kIsArrayAlloc; | ||||||||||
| cppres->fFlags |= CPPInstance::kIsNoConstruct; | ||||||||||
| break; | ||||||||||
| default: | ||||||||||
| break; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // an argument that the called function releases belongs to C++ from now | ||||||||||
| // on, else Python frees it a second time | ||||||||||
| const Py_ssize_t nargs = cpyrt_PyArgs_GET_SIZE(args, nargsf); | ||||||||||
| const Py_ssize_t numKwds = | ||||||||||
| kwds && PyTuple_CheckExact(kwds) ? PyTuple_GET_SIZE(kwds) : 0; | ||||||||||
| // the keyword values follow the positional ones in call order, so their | ||||||||||
| // names are what tell which parameter each one belongs to | ||||||||||
| PyObject* argnames = numKwds ? method->GetSignatureNames() : nullptr; | ||||||||||
|
|
||||||||||
| const bool isBound = pymeth->fSelf != nullptr; | ||||||||||
| // a place in the array becomes a slot with this shift | ||||||||||
| const Py_ssize_t argShift = | ||||||||||
| method->GetDeallocArgShift(isBound, args, nargs); | ||||||||||
| // a parameter index becomes a slot with this one | ||||||||||
| const Py_ssize_t paramShift = method->GetDeallocParamShift(); | ||||||||||
|
Comment on lines
+204
to
+208
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are bool CPPMethod::NeedsSelf() {
return interop::IsNonStaticMetod(GetMethod());
}
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will prefer removing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, I would just put casts for every type of function and calculate offsets, but since CppJIT's style is mostly based on inheritance structure I decided to go this way. And for your suggestions: 1) for using IsStaticMethod(method->getMethod()) i need to add a virtual function to the PyCallable, so there will be no gain in terms of number of functions, and code will be more messy because there will be exceptions for some classes, eg. TPythonCallback. Py_ssize_t cpyrt::CPPClassMethod::GetDeallocArgShift(bool isBound,
PyObject* const* args,
Py_ssize_t nargs) {
// a static method takes no object argument, so a bound call drops self
if (isBound)
return 0;
// Call function above drops a leading instance that the call does not need,
// and then args[0] belongs to no parameter. The check below repeats the one
// in Call(), so a change to either one belongs in both.
if (nargs > 0 && CPPInstance_Check(args[0]) && fArgsRequired <= nargs - 1 &&
interop::IsSubclass(((CPPInstance*)args[0])->ObjectIsA(), GetScope()))
return -1;
return 0;
}fArgsRequired is a protected member, of course there are solutions but things get messy as we try to move offset logic into handleReturn. So my opinion is: we either use casts and do everything in handleReturn (I could not find any example of this kind of code in CppJIT),, or keep these getArg/ParamShift functions, because otherwise code just becomes low-quality |
||||||||||
| // the array holds the positional values first, then the keyword ones | ||||||||||
| for (Py_ssize_t i = 0; i < nargs + numKwds; i++) { | ||||||||||
| // only a C++ object can hold memory that the callee releases | ||||||||||
| if (!CPPInstance_Check(args[i])) | ||||||||||
| continue; | ||||||||||
|
Comment on lines
+212
to
+213
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you suggest this for LowLevelView? It is the content of next PR, so this PR only touch to the CPPInstance |
||||||||||
|
|
||||||||||
| Py_ssize_t slot = -1; // no slot of its own | ||||||||||
| if (i < nargs) | ||||||||||
| slot = i + argShift; | ||||||||||
| else { | ||||||||||
| // kwds runs parallel to the keyword part of the array | ||||||||||
| // the place of the name in the signature is the parameter index | ||||||||||
| for (Py_ssize_t iarg = 0; iarg < PyTuple_GET_SIZE(argnames); iarg++) { | ||||||||||
| if (PyUnicode_Compare(PyTuple_GET_ITEM(argnames, iarg), | ||||||||||
| PyTuple_GET_ITEM(kwds, i - nargs)) == 0) { | ||||||||||
| slot = iarg + paramShift; | ||||||||||
| break; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| // the callee drops this argument, or no name of it matches a parameter | ||||||||||
| if (slot < 0) | ||||||||||
| continue; | ||||||||||
|
|
||||||||||
| const interop::DeallocType DT = method->GetDeallocBehaviour((size_t)slot); | ||||||||||
| if (DT == interop::DeallocType::None || | ||||||||||
| DT == interop::DeallocType::Opaque) | ||||||||||
| continue; | ||||||||||
| ((CPPInstance*)args[i])->CppOwns(); | ||||||||||
| } | ||||||||||
| Py_XDECREF(argnames); | ||||||||||
|
|
||||||||||
| // if this new object falls inside self, make sure its lifetime is proper | ||||||||||
| if (!(pymeth->fMethodInfo->fFlags & CallContext::kNeverLifeLine)) { | ||||||||||
|
|
@@ -576,7 +652,8 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, | |||||||||
| if (!NoImplicit(&ctxt)) | ||||||||||
| ctxt.fFlags |= CallContext::kAllowImplicit; // no two rounds needed | ||||||||||
| PyObject* result = methods[0]->Call(im_self, args, nargsf, kwds, &ctxt); | ||||||||||
| return HandleReturn(pymeth, im_self, result); | ||||||||||
| return HandleReturn(pymeth, im_self, result, methods[0], args, nargsf, | ||||||||||
| kwds); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // otherwise, handle overloading | ||||||||||
|
|
@@ -599,7 +676,8 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, | |||||||||
| ctxt.fFlags |= CallContext::kAllowImplicit; | ||||||||||
| PyObject* result = memoized_pc->Call(im_self, args, nargsf, kwds, &ctxt); | ||||||||||
| if (result) | ||||||||||
| return HandleReturn(pymeth, im_self, result); | ||||||||||
| return HandleReturn(pymeth, im_self, result, memoized_pc, args, nargsf, | ||||||||||
| kwds); | ||||||||||
|
|
||||||||||
| // fall through: python is dynamic, and so, the hashing isn't infallible | ||||||||||
| ctxt.fFlags &= ~(CallContext::kAllowImplicit | CallContext::kPyException | | ||||||||||
|
|
@@ -656,7 +734,8 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, | |||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return HandleReturn(pymeth, im_self, result); | ||||||||||
| return HandleReturn(pymeth, im_self, result, methods[i], args, nargsf, | ||||||||||
| kwds); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // else failure .. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to
__is_construct__.