Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/cpyrt/CPPMethod.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extern PyObject* gBusException;
extern PyObject* gSegvException;
extern PyObject* gIllException;
extern PyObject* gAbrtException;
extern bool gUseAllocAnalyzer;
} // namespace cppjit::cpyrt

//- public helper ------------------------------------------------------------
Expand Down Expand Up @@ -749,6 +750,28 @@ PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) {

bool cpyrt::CPPMethod::IsConst() { return interop::IsConstMethod(GetMethod()); }

//----------------------------------------------------------------------------
// FIXME: For now every allocation is assumed to be done with `new`
// will be fixed soon. Also the reason function returns an AllocType
// instead of bool, IsAllocator function from CppInterOp changed a little,
// but these changes did not merged to main yet, another reason is next PR
// will add user-optional analyzer so the function needs to return allocation
// way
interop::AllocType cpyrt::CPPMethod::GetAllocBehaviour() {
if (fAllocType.has_value())
return *fAllocType;
if (interop::IsAllocator(GetMethod())) {
fAllocType = interop::AllocType::New;
return interop::AllocType::New;
}
if (gUseAllocAnalyzer) {
interop::AllocType AT = interop::GetAllocType(GetMethod());
fAllocType = AT;
return AT;
}
fAllocType = interop::AllocType::None;
return interop::AllocType::None;
}
//----------------------------------------------------------------------------
PyObject* cpyrt::CPPMethod::GetScopeProxy() {
// Get or build the scope of this method.
Expand Down
3 changes: 3 additions & 0 deletions src/cpyrt/CPPMethod.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "PyCallable.h"

// Standard
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -63,6 +64,7 @@ class CPPMethod : public PyCallable {
PyObject* GetCoVarNames() override;
PyObject* GetArgDefault(int iarg, bool silent = true) override;
bool IsConst() override;
cppjit::interop::AllocType GetAllocBehaviour() override;

PyObject* GetScopeProxy() override;
cppjit::interop::TCppFuncAddr_t GetFunctionAddress() override;
Expand Down Expand Up @@ -117,6 +119,7 @@ class CPPMethod : public PyCallable {
protected:
// cached value that doubles as initialized flag (uninitialized if -1)
int fArgsRequired;
std::optional<cppjit::interop::AllocType> fAllocType;
};

} // namespace cppjit::cpyrt
Expand Down
6 changes: 6 additions & 0 deletions src/cpyrt/CPPOverload.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self,
CPPInstance* cppres =
(CPPInstance*)(CPPInstance_Check(result) ? result : nullptr);

interop::AllocType AT =
pymeth->fMethodInfo->fMethods[0]->GetAllocBehaviour();
if (AT != interop::AllocType::None && AT != interop::AllocType::Null &&
AT != interop::AllocType::Unknown)
pymeth->fMethodInfo->fFlags |= CallContext::kIsCreator;

// if this method creates new objects, always take ownership
if (IsCreator(pymeth->fMethodInfo->fFlags)) {

Expand Down
3 changes: 3 additions & 0 deletions src/cpyrt/PyCallable.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class PyCallable {
virtual PyObject* GetCoVarNames() = 0;
virtual PyObject* GetArgDefault(int /* iarg */, bool silent = true) = 0;
virtual bool IsConst() { return false; }
virtual cppjit::interop::AllocType GetAllocBehaviour() {
return cppjit::interop::AllocType::None;
}

virtual PyObject* GetScopeProxy() = 0;
virtual cppjit::interop::TCppFuncAddr_t GetFunctionAddress() = 0;
Expand Down
17 changes: 17 additions & 0 deletions src/cpyrt/cppjit_interop.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ struct ObjectRef {
friend bool operator==(ObjectRef a, ObjectRef b) { return a.data == b.data; }
friend bool operator!=(ObjectRef a, ObjectRef b) { return !(a == b); }
};

enum class AllocType : unsigned char {
None,
New,
NewArr,
Malloc,
Unknown,
CustomAlloc,
Null,
OperatorNew,
OperatorNewArr
};
} // namespace Cpp

template <> struct std::hash<Cpp::DeclRef> {
Expand Down Expand Up @@ -122,6 +134,7 @@ typedef Cpp::ObjectRef TCppObject_t;
typedef Cpp::FuncRef TCppMethod_t;
typedef size_t TCppIndex_t;
typedef void* TCppFuncAddr_t;
typedef Cpp::AllocType AllocType;

// direct interpreter access -------------------------------------------------
CPPJIT_IMPORT
Expand Down Expand Up @@ -368,6 +381,10 @@ CPPJIT_IMPORT
std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true);
CPPJIT_IMPORT
bool IsConstMethod(TCppMethod_t);
CPPJIT_IMPORT
bool IsAllocator(TCppMethod_t);
CPPJIT_IMPORT
AllocType GetAllocType(TCppMethod_t);
// Templated method/function reflection information
// ------------------------------------
CPPJIT_IMPORT
Expand Down
17 changes: 17 additions & 0 deletions src/cpyrt/cpyrtModule.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ PyObject* gAbrtException = nullptr;
std::unordered_set<interop::TCppScope_t> gPinnedTypes;
std::ostringstream gCapturedError;
std::streambuf* gOldErrorBuffer = nullptr;
bool gUseAllocAnalyzer = false;

std::unordered_map<std::string, std::vector<PyObject*>>& pythonizations() {
static std::unordered_map<std::string, std::vector<PyObject*>> pyzMap;
Expand Down Expand Up @@ -1012,6 +1013,20 @@ static PyObject* EndCaptureStderr(PyObject*, PyObject*) {

return Py_BuildValue("s", capturedError.c_str());
}

//----------------------------------------------------------------------------
static PyObject* SetUseAllocAnalyzer(PyObject*, PyObject* args) {
// Set allocation-analyzer policy, disabled by default
// Usage: enabling ->SetUseAllocAnalyzer(True) / SetUseAllocAnalyzer(1)
// disabling ->SetUseAllocAnalyzer(False) / SetUseAllocAnalyzer(0)
int enable = 0;
if (!PyArg_ParseTuple(args, const_cast<char*>("p"), &enable))
return nullptr;

gUseAllocAnalyzer = enable;

Py_RETURN_NONE;
}
} // unnamed namespace

//- data -----------------------------------------------------------------------
Expand Down Expand Up @@ -1061,6 +1076,8 @@ static PyMethodDef gcpyrtMethods[] = {
METH_NOARGS, (char*)"Begin capturing stderr to a in memory buffer."},
{(char*)"_end_capture_stderr", (PyCFunction)EndCaptureStderr, METH_NOARGS,
(char*)"End capturing stderr and returns the captured buffer."},
{(char*)"SetUseAllocAnalyzer", (PyCFunction)SetUseAllocAnalyzer,
METH_VARARGS, (char*)"Enable/disable memory-allocation analyzer."},
{nullptr, nullptr, 0, nullptr}};

struct module_state {
Expand Down
5 changes: 5 additions & 0 deletions src/interop/cpp_cppjit.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ typedef Cpp::FuncRef TCppMethod_t;
typedef Cpp::InterpRef TInterp_t;
typedef size_t TCppIndex_t;
typedef void* TCppFuncAddr_t;
typedef Cpp::AllocType AllocType;

// direct interpreter access -------------------------------------------------
RPY_EXPORTED
Expand Down Expand Up @@ -306,6 +307,10 @@ RPY_EXPORTED
std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true);
RPY_EXPORTED
bool IsConstMethod(TCppMethod_t);
RPY_EXPORTED
bool IsAllocator(TCppMethod_t);
RPY_EXPORTED
AllocType GetAllocType(TCppMethod_t);
// Templated method/function reflection information
// ------------------------------------
RPY_EXPORTED
Expand Down
10 changes: 10 additions & 0 deletions src/interop/interop_wrapper.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,16 @@ interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) {
return Cpp::GetFunctionReturnType(method);
}

bool interop::IsAllocator(TCppMethod_t method) {
std::lock_guard<std::recursive_mutex> Lock(InterOpMutex);
return Cpp::IsAllocator(method);
}

interop::AllocType interop::GetAllocType(TCppMethod_t method) {
std::lock_guard<std::recursive_mutex> Lock(InterOpMutex);
return Cpp::GetAllocType(method);
}

std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) {
std::lock_guard<std::recursive_mutex> Lock(InterOpMutex);
return Cpp::GetTypeAsString(
Expand Down
1 change: 1 addition & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dictnames = advancedcpp \
doc_helper \
example01 \
fragile \
memory_analysis \
operators \
overloads \
pythonizables \
Expand Down
9 changes: 9 additions & 0 deletions test/cpp/memory_analysis.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "memory_analysis.h"
__attribute__((malloc)) memAnalysisKlass* allocTest() {
return new memAnalysisKlass;
}

__attribute__((ownership_returns(malloc))) memAnalysisKlass*
allocTestReturns() {
return new memAnalysisKlass;
}
15 changes: 15 additions & 0 deletions test/cpp/memory_analysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef MEMORY_ANALYSIS_H
#define MEMORY_ANALYSIS_H

#include <new>
#include <stdlib.h>

class memAnalysisKlass {
int val;
};
__attribute__((malloc)) memAnalysisKlass* allocTest();
__attribute__((ownership_returns(malloc))) memAnalysisKlass* allocTestReturns();

inline memAnalysisKlass* allocNew() { return new memAnalysisKlass; }
inline memAnalysisKlass* allocNew2() { return new memAnalysisKlass; }
Comment on lines +13 to +14

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the function names be a bit more descriptive? In the context of the test. Example:
AnalysisOnAllocNew & AnalysisOffAllocNew?

#endif // MEMORY_ANALYSIS_H
48 changes: 48 additions & 0 deletions test/test_memoryanalysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import py
from support import setup_make

currpath = py.path.local(__file__).dirpath()
test_dct = str(currpath.join("cpp/memory_analysisDict"))


def setup_module(mod):
setup_make("memory_analysis")


class TestMEMORYANALYSIS:
def setup_class(cls):
cls.test_dct = test_dct
import cppjit

cls.memory_analysis = cppjit.load_reflection_info(cls.test_dct)

def test01_malloc_attr(self):
import cppjit

obj = cppjit.gbl.allocTest()
assert type(obj) == cppjit.gbl.memAnalysisKlass
assert obj.__python_owns__

def test02_ownership_returns_attr(self):
import cppjit

obj = cppjit.gbl.allocTestReturns()
assert type(obj) == cppjit.gbl.memAnalysisKlass
assert obj.__python_owns__

def test03_analyzer_new(self):
import cppjit

cppjit._backend.SetUseAllocAnalyzer(True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a wrapper in __init__.py, such that the user can directly do
cppjit.use_alloc_analyzer(...). Note that in Python, function names should be in snake case.

obj = cppjit.gbl.allocNew()
assert type(obj) == cppjit.gbl.memAnalysisKlass
assert obj.__python_owns__

def test04_analyzer_new_default(self):
import cppjit

cppjit._backend.SetUseAllocAnalyzer(False)
obj = cppjit.gbl.allocNew2()
assert type(obj) == cppjit.gbl.memAnalysisKlass
assert not (obj.__python_owns__)
Comment on lines +44 to +47

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a test for reusing existing results... i.e. already analyzed, but use_alloc_analyzer(False).

obj.__python_owns__ = True
Loading