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
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ include(GNUInstallDirs)
# This option won't make a lot of sense since we only ship the shared library in site-packages
# Perhaps this should permanently be OFF and users can build their own CppInterOp if they want to run the tests?
option(CPPJIT_ENABLE_CPPINTEROP_TESTS "enable CppInterOp tests" OFF)
set(CPPINTEROP_GIT_REPOSITORY "https://github.com/compiler-research/CppInterOp.git" CACHE STRING "")
set(CPPINTEROP_GIT_TAG "8d624c621a4b95e36ff73ac708c85a768287478f" CACHE STRING "")
set(CPPINTEROP_GIT_REPOSITORY "https://github.com/keremsahn/CppInterOp.git" CACHE STRING "")
set(CPPINTEROP_GIT_TAG "attr-design" CACHE STRING "")
set(CPPINTEROP_SOURCE_DIR "" CACHE PATH
"Override default CppInterOp built by ExternalProject_Add, with a path to local CppInterOp source")

Expand Down
8 changes: 8 additions & 0 deletions src/cpyrt/CPPMethod.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,14 @@ PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) {

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

//----------------------------------------------------------------------------
interop::AllocType cpyrt::CPPMethod::GetAllocBehaviour() {
if (fAllocType.has_value())
return *fAllocType;
interop::AllocType attrResult = interop::IsAllocator(GetMethod());
fAllocType = attrResult;
return attrResult;
}
//----------------------------------------------------------------------------
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 @@ -62,6 +63,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;
interop::TCppFuncAddr_t GetFunctionAddress() override;
Expand Down Expand Up @@ -116,6 +118,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;

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.

Suggested change
return cppjit::interop::AllocType::None;
return cppjit::interop::AllocType::Unknown;

?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Only class does not override GetAllocBehaviour is TPythonCallBack, and from my understanding this class is for some sort of python function, not C/C++, therefore I thought it would make sense to return None

}

virtual PyObject* GetScopeProxy() = 0;
virtual interop::TCppFuncAddr_t GetFunctionAddress() = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/interop/cppjit_interop.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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 @@ -297,6 +298,8 @@ RPY_EXPORTED
std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true);
RPY_EXPORTED
bool IsConstMethod(TCppMethod_t);
RPY_EXPORTED
AllocType IsAllocator(TCppMethod_t);
// Templated method/function reflection information
// ------------------------------------
RPY_EXPORTED
Expand Down
5 changes: 5 additions & 0 deletions src/interop/interop_wrapper.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,11 @@ interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) {
return Cpp::GetFunctionReturnType(method);
}

interop::AllocType interop::IsAllocator(TCppMethod_t method) {
std::lock_guard<std::recursive_mutex> Lock(InterOpMutex);
return Cpp::IsAllocator(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/MemoryOwnership/MemOwnrship.apinotes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Name: MemOwnrship
Functions:
- Name: memOwnAllocGlobal
SwiftReturnOwnership: cppAllocNew
Tags:
- Name: memOwn
Methods:
- Name: memOwnAllocator
SwiftReturnOwnership: cppAllocNew
10 changes: 10 additions & 0 deletions test/cpp/MemoryOwnership/memory_analysis_redecl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef MEMORY_ANALYSIS_REDECL_H
#define MEMORY_ANALYSIS_REDECL_H
#include "../memory_analysis.h"

namespace memory {
[[clang::annotate("cppAllocNew")]]
memOwn* allocDefaultMemOwn();
}

#endif
1 change: 1 addition & 0 deletions test/cpp/MemoryOwnership/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module MemOwnrship { header "../memory_analysis.h" }
24 changes: 24 additions & 0 deletions test/cpp/memory_analysis.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "memory_analysis.h"

namespace memory {

__attribute__((malloc)) memAnalysisKlass* mallocAttr() {
return new memAnalysisKlass;
}

__attribute__((ownership_returns(malloc))) memAnalysisKlass*
ownershipReturnsAttr() {
return new memAnalysisKlass;
}

// Expected to not return ownership when analysis is off, and there is just
// attr-check
memAnalysisKlass* noAttr() { return new memAnalysisKlass; }

memOwn* memOwnAllocGlobal() { return (memOwn*)malloc(sizeof(memOwn)); }

memOwn* allocDefaultMemOwn() { return new memOwn; }

memOwn* noAttrAlloc() { return new memOwn; }

} // namespace memory
36 changes: 36 additions & 0 deletions test/cpp/memory_analysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef MEMORY_ANALYSIS_H
#define MEMORY_ANALYSIS_H

#include <new>
#include <stdlib.h>
namespace memory {

class memAnalysisKlass {
public:
int val;
};
__attribute__((malloc)) memAnalysisKlass* mallocAttr();
__attribute__((ownership_returns(malloc))) memAnalysisKlass*
ownershipReturnsAttr();
memAnalysisKlass* noAttr();

struct memOwn {
int val;
memOwn(int value) : val(value) {}
memOwn() { val = 0; }
// Attribute injected by APINotes
static memOwn* memOwnAllocator(int x) { return new memOwn(x); }
};

// Attribute injected by APINotes
memOwn* memOwnAllocGlobal();

// Attribute injected by redeclaration
memOwn* allocDefaultMemOwn();

// No ownership attribute anywhere
memOwn* noAttrAlloc();

} // namespace memory

#endif // MEMORY_ANALYSIS_H
102 changes: 102 additions & 0 deletions test/test_memoryanalysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import os
import subprocess
import sys

import py
from pytest import mark
from support import IS_CLING, setup_make

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

FLAGS = "-fmodules -fimplicit-module-maps -fapinotes-modules"
IN_CHILD = "-fapinotes-modules" in os.getenv("CPPINTEROP_EXTRA_INTERPRETER_ARGS", "")


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


@mark.skipif(
IN_CHILD or IS_CLING,
reason="Cling asserts in collectModuleMaps when built with " + FLAGS,
)
def test00_driver():
env = os.environ.copy()
env["CPPINTEROP_EXTRA_INTERPRETER_ARGS"] = (
env.get("CPPINTEROP_EXTRA_INTERPRETER_ARGS", "") + " " + FLAGS
)
subprocess.check_call([sys.executable, "-m", "pytest", __file__], env=env)


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

cppjit.add_include_path(str(currpath.join("cpp", "MemoryOwnership")))
cppjit.include("../memory_analysis.h")
cppjit.include("memory_analysis_redecl.h")
cls.memory_analysis = cppjit.load_library(cls.test_dct + ".so")

def test01_malloc_attr(self):
import cppjit

obj = cppjit.gbl.memory.mallocAttr()
assert type(obj) == cppjit.gbl.memory.memAnalysisKlass
assert obj.__python_owns__

def test02_ownership_returns_attr(self):
import cppjit

obj = cppjit.gbl.memory.ownershipReturnsAttr()
assert type(obj) == cppjit.gbl.memory.memAnalysisKlass
assert obj.__python_owns__

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 also need tests for

assert not obj.__python__owns__


def test03_no_attr(self):
import cppjit

obj = cppjit.gbl.memory.noAttr()
assert type(obj) == cppjit.gbl.memory.memAnalysisKlass
assert not obj.__python_owns__
obj.__python_owns__ = True

def test04_redecl_attr(self):
import cppjit

obj = cppjit.gbl.memory.allocDefaultMemOwn()
assert type(obj) == cppjit.gbl.memory.memOwn
assert obj.__python_owns__

def test05_redecl_no_attr(self):
import cppjit

obj = cppjit.gbl.memory.noAttrAlloc()
assert type(obj) == cppjit.gbl.memory.memOwn
assert not obj.__python_owns__
obj.__python_owns__ = True


@mark.skipif(not IN_CHILD, reason="needs " + FLAGS)
class TestMEMORYANALYSIS_APINOTES:
def setup_class(cls):
cls.test_dct = test_dct
import cppjit

cppjit.add_include_path(str(currpath.join("cpp", "MemoryOwnership")))
cppjit.include("../memory_analysis.h")
cls.memory_analysis = cppjit.load_library(cls.test_dct + ".so")

def test01_apinotes_attr_method(self):
import cppjit

obj = cppjit.gbl.memory.memOwn.memOwnAllocator(5)
assert type(obj) == cppjit.gbl.memory.memOwn
assert obj.__python_owns__

def test02_apinotes_attr_func(self):
import cppjit

obj = cppjit.gbl.memory.memOwnAllocGlobal()
assert type(obj) == cppjit.gbl.memory.memOwn
assert obj.__python_owns__
Loading