Skip to content

Commit 602a1a5

Browse files
committed
[GR-31701] Improve support for lazy adding/creating new native class methods
PullRequest: graalpython/1820
2 parents 1efc56f + f0d0f68 commit 602a1a5

File tree

24 files changed

+600
-232
lines changed

24 files changed

+600
-232
lines changed

graalpython/com.oracle.graal.python.cext/src/capi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ void initialize_hashes();
325325
#define JWRAPPER_INQUIRY 24
326326
#define JWRAPPER_DELITEM 25
327327
#define JWRAPPER_GETITEM 26
328+
#define JWRAPPER_INITPROC 29
328329

329330
#define TDEBUG __builtin_debugtrap()
330331
#define get_method_flags_wrapper(flags) \

graalpython/com.oracle.graal.python.cext/src/descrobject.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,19 @@ int PyMethodDescr_Check(PyObject* method) {
6767
return UPCALL_CEXT_I(_jls_PyMethodDescr_Check, native_to_java(method));
6868
}
6969

70-
typedef PyObject* (*new_classmethod_fun_t)(PyTypeObject*, void*, void*);
71-
72-
UPCALL_TYPED_ID(PyDescr_NewClassMethod, new_classmethod_fun_t);
70+
typedef PyObject* (*PyDescr_NewClassMethod_fun_t)(void* name,
71+
const char* doc,
72+
int flags,
73+
int wrapper,
74+
void* methObj,
75+
void* primary);
76+
UPCALL_TYPED_ID(PyDescr_NewClassMethod, PyDescr_NewClassMethod_fun_t);
7377
PyObject* PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method) {
74-
return _jls_PyDescr_NewClassMethod(native_type_to_java(type), native_pointer_to_java(method->ml_name), native_pointer_to_java(method->ml_meth));
78+
int flags = method->ml_flags;
79+
return _jls_PyDescr_NewClassMethod(polyglot_from_string(method->ml_name, SRC_CS),
80+
method->ml_doc,
81+
flags,
82+
get_method_flags_wrapper(flags),
83+
native_pointer_to_java(method->ml_meth),
84+
type);
7585
}

graalpython/com.oracle.graal.python.cext/src/methodobject.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
4444

4545
PyTypeObject PyCFunction_Type = PY_TRUFFLE_TYPE_WITH_VECTORCALL("builtin_function_or_method", &PyType_Type, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | _Py_TPFLAGS_HAVE_VECTORCALL, sizeof(PyCFunctionObject), offsetof(PyCFunctionObject, vectorcall));
4646

47+
typedef PyObject* (*PyCFunction_NewEx_fun_t)(void* name,
48+
void* methObj,
49+
int flags,
50+
int wrapper,
51+
void* self,
52+
void* module,
53+
const char* doc);
54+
UPCALL_TYPED_ID(PyCFunction_NewEx, PyCFunction_NewEx_fun_t);
4755
PyObject* PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module) {
48-
return to_sulong(polyglot_invoke(PY_TRUFFLE_CEXT,
49-
"PyCFunction_NewEx",
50-
polyglot_from_string((const char*)(ml->ml_name), SRC_CS),
56+
return _jls_PyCFunction_NewEx(polyglot_from_string(ml->ml_name, SRC_CS),
5157
function_pointer_to_java(ml->ml_meth),
58+
ml->ml_flags,
5259
get_method_flags_wrapper(ml->ml_flags),
5360
native_to_java(self),
5461
native_to_java(module),
55-
ml->ml_doc));
62+
ml->ml_doc);
5663
}

graalpython/com.oracle.graal.python.cext/src/moduleobject.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,21 @@ PyModuleDef_Init(struct PyModuleDef* def)
5757
return (PyObject*)def;
5858
}
5959

60+
// cls dict name cfunc flags sig doc
61+
typedef int (*AddFunction_fun_t)(PyObject *, PyObject *, void *, void *, int , int, char *);
62+
UPCALL_TYPED_ID(AddFunction, AddFunction_fun_t);
6063
int PyModule_AddFunctions(PyObject* mod, PyMethodDef* methods) {
6164
if (!methods) {
6265
return -1;
6366
}
6467
int idx = 0;
6568
PyMethodDef def = methods[idx];
6669
while (def.ml_name != NULL) {
67-
polyglot_invoke(PY_TRUFFLE_CEXT,
68-
"AddFunction",
69-
native_to_java(mod),
70+
_jls_AddFunction(native_to_java(mod),
7071
NULL,
71-
polyglot_from_string((const char*)(def.ml_name), SRC_CS),
72+
polyglot_from_string(def.ml_name, SRC_CS),
7273
function_pointer_to_java(def.ml_meth),
74+
def.ml_flags,
7375
get_method_flags_wrapper(def.ml_flags),
7476
(def.ml_doc ? def.ml_doc : ""));
7577
def = methods[++idx];

graalpython/com.oracle.graal.python.cext/src/typeobject.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,18 +384,18 @@ int add_getset(PyTypeObject* cls, PyObject* type_dict, char* name, getter getter
384384
return _jls_AddGetSet(cls, native_to_java(type_dict), polyglot_from_string(name, SRC_CS), getter_resolved, setter_resolved, doc, closure);
385385
}
386386

387+
// cls dict name cfunc flags sig doc
388+
typedef int (*AddFunction_fun_t)(PyTypeObject *, PyObject *, void *, void *, int , int, char *);
389+
UPCALL_TYPED_ID(AddFunction, AddFunction_fun_t);
387390
static void add_method_or_slot(PyTypeObject* cls, PyObject* type_dict, char* name, void* result_conversion, void* meth, int flags, int signature, char* doc) {
388391
void *resolved_meth = function_pointer_to_java(meth);
389-
polyglot_invoke(PY_TRUFFLE_CEXT,
390-
"AddFunction",
391-
cls,
392+
_jls_AddFunction(cls,
392393
native_to_java(type_dict),
393394
polyglot_from_string(name, SRC_CS),
394395
native_pointer_to_java(result_conversion != NULL ? pytruffle_decorate_function(resolved_meth, result_conversion) : resolved_meth),
396+
flags,
395397
(signature != 0 ? signature : get_method_flags_wrapper(flags)),
396-
doc,
397-
(flags) > 0 && ((flags) & METH_CLASS) != 0,
398-
(flags) > 0 && ((flags) & METH_STATIC) != 0);
398+
doc);
399399
}
400400

401401
#define ADD_MEMBER(__javacls__, __tpdict__, __mname__, __mtype__, __moffset__, __mflags__, __mdoc__) \
@@ -586,7 +586,7 @@ int PyType_Ready(PyTypeObject* cls) {
586586
ADD_SLOT_CONV("__next__", NULL, cls->tp_iternext, -1, JWRAPPER_ITERNEXT);
587587
ADD_SLOT("__get__", cls->tp_descr_get, -3);
588588
ADD_SLOT_PRIMITIVE("__set__", cls->tp_descr_set, -3);
589-
ADD_SLOT_PRIMITIVE("__init__", cls->tp_init, METH_KEYWORDS | METH_VARARGS);
589+
ADD_SLOT_CONV("__init__", NULL, cls->tp_init, METH_KEYWORDS | METH_VARARGS, JWRAPPER_INITPROC);
590590
ADD_SLOT_CONV("__alloc__", NULL, cls->tp_alloc, -2, JWRAPPER_ALLOC);
591591
ADD_SLOT("__new__", cls->tp_new, METH_KEYWORDS | METH_VARARGS);
592592
ADD_SLOT_PRIMITIVE("__free__", cls->tp_free, -1);

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_descr.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939

40-
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare
40+
from . import CPyExtType, CPyExtTestCase, CPyExtFunction, unhandled_error_compare
4141
__dir__ = __file__.rpartition("/")[0]
4242

4343

@@ -46,6 +46,141 @@ def _reference_classmethod(args):
4646
return classmethod(args[0])()
4747
raise TypeError
4848

49+
class TestDescrObject(object):
50+
51+
def test_new_classmethod(self):
52+
53+
TestNewClassMethod = CPyExtType("TestNewClassMethod",
54+
"""
55+
static PyObject * c_void_p_from_param(PyObject *self, PyObject *value) {
56+
/* just returns self which should be the class */
57+
return self;
58+
}
59+
static PyMethodDef c_void_p_method = { "from_param", c_void_p_from_param, METH_O };
60+
""",
61+
post_ready_code="""
62+
PyObject *meth = PyDescr_NewClassMethod(&TestNewClassMethodType, &c_void_p_method);
63+
if (!meth) {
64+
return NULL;
65+
}
66+
int x = PyDict_SetItemString(TestNewClassMethodType.tp_dict, c_void_p_method.ml_name, meth);
67+
Py_DECREF(meth);
68+
if (x == -1) {
69+
return NULL;
70+
}
71+
""",
72+
)
73+
74+
tester = TestNewClassMethod()
75+
assert tester.from_param(123) == TestNewClassMethod
76+
77+
def test_new_descr(self):
78+
C = CPyExtType("C_",
79+
'''
80+
typedef struct A_Struct A_Object;
81+
82+
struct A_Struct {
83+
PyObject_HEAD
84+
int some_int;
85+
};
86+
87+
PyTypeObject A_Type = {
88+
PyVarObject_HEAD_INIT(NULL, 0)
89+
.tp_name = "A",
90+
.tp_basicsize = sizeof(A_Object),
91+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
92+
};
93+
94+
static PyObject *
95+
foo(PyObject *type, PyObject *value) {
96+
Py_INCREF(Py_None);
97+
return Py_None;
98+
}
99+
100+
static PyMethodDef foo_method = {
101+
"foo", foo, METH_O
102+
};
103+
104+
static PyObject* B_new(PyTypeObject* type, PyObject* a, PyObject* b) {
105+
PyTypeObject *result;
106+
PyMethodDef *ml;
107+
PyObject *meth;
108+
int x;
109+
result = (PyTypeObject *)PyType_Type.tp_new(type, a, b);
110+
if (result == NULL)
111+
return NULL;
112+
113+
ml = &foo_method;
114+
115+
meth = PyDescr_NewClassMethod(result, ml);
116+
if (!meth) {
117+
Py_DECREF(result);
118+
return NULL;
119+
}
120+
x = PyDict_SetItemString(result->tp_dict,
121+
ml->ml_name,
122+
meth);
123+
Py_DECREF(meth);
124+
if (x == -1) {
125+
Py_DECREF(result);
126+
return NULL;
127+
}
128+
129+
return (PyObject *) result;
130+
}
131+
132+
PyTypeObject B_Type = {
133+
PyVarObject_HEAD_INIT(NULL, 0)
134+
.tp_name = "B",
135+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
136+
.tp_new = B_new,
137+
};
138+
139+
static PyObject* C_new(PyTypeObject* cls, PyObject* a, PyObject* b) {
140+
return cls->tp_alloc(cls, 0);
141+
}
142+
143+
static int
144+
C_traverse(C_Object *self, visitproc visit, void *arg) {
145+
// This helps to avoid setting 'Py_TPFLAGS_HAVE_GC'
146+
// see typeobject.c:inherit_special:241
147+
return 0;
148+
}
149+
150+
static int
151+
C_clear(C_Object *self) {
152+
// This helps to avoid setting 'Py_TPFLAGS_HAVE_GC'
153+
// see typeobject.c:inherit_special:241
154+
return 0;
155+
}
156+
157+
static int
158+
C_init(C_Object *self, PyObject *a, PyObject *k)
159+
{
160+
return 0;
161+
}
162+
163+
''',
164+
tp_traverse="(traverseproc)C_traverse",
165+
tp_clear="(inquiry)C_clear",
166+
tp_new="C_new",
167+
tp_init="(initproc)C_init",
168+
ready_code='''
169+
if (PyType_Ready(&A_Type) < 0)
170+
return NULL;
171+
172+
B_Type.tp_base = &PyType_Type;
173+
if (PyType_Ready(&B_Type) < 0)
174+
return NULL;
175+
176+
Py_TYPE(&C_Type) = &B_Type;
177+
C_Type.tp_base = &A_Type;''',
178+
)
179+
180+
class bar(C):
181+
pass
182+
assert bar().foo(None) is None
183+
49184
class TestDescr(CPyExtTestCase):
50185

51186
def compile_module(self, name):

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,6 @@ def test_repr(self):
183183
def test_base_type(self):
184184
AcceptableBaseType = CPyExtType("AcceptableBaseType",
185185
'''
186-
static PyObject *
187-
TestBase_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
188-
{
189-
return PyType_Type.tp_new(type, args, kwds);
190-
}
191186
PyTypeObject TestBase_Type = {
192187
PyVarObject_HEAD_INIT(NULL, 0)
193188
.tp_name = "TestBase",

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,17 @@ public void initialize(Python3Core core) {
7676
RootCallTarget callTarget = core.getLanguage().createCachedCallTarget(l -> new BuiltinFunctionRootNode(l, builtin, factory, declaresExplicitSelf), factory.getNodeClass(),
7777
builtin.name());
7878
Object builtinDoc = builtin.doc().isEmpty() ? PNone.NONE : builtin.doc();
79+
int flags = PBuiltinFunction.getFlags(builtin, callTarget);
7980
if (constructsClass != PythonBuiltinClassType.nil) {
8081
assert !builtin.isGetter() && !builtin.isSetter() && !builtin.isClassmethod() && !builtin.isStaticmethod();
8182
// we explicitly do not make these "staticmethods" here, since CPython also doesn't
8283
// for builtin types
83-
PBuiltinFunction newFunc = core.factory().createBuiltinFunction(__NEW__, constructsClass, numDefaults(builtin), callTarget);
84+
PBuiltinFunction newFunc = core.factory().createBuiltinFunction(__NEW__, constructsClass, numDefaults(builtin), flags, callTarget);
8485
PythonBuiltinClass builtinClass = core.lookupType(constructsClass);
8586
builtinClass.setAttributeUnsafe(__NEW__, newFunc);
8687
builtinClass.setAttribute(__DOC__, builtinDoc);
8788
} else {
88-
PBuiltinFunction function = core.factory().createBuiltinFunction(builtin.name(), null, numDefaults(builtin), callTarget);
89+
PBuiltinFunction function = core.factory().createBuiltinFunction(builtin.name(), null, numDefaults(builtin), flags, callTarget);
8990
function.setAttribute(__DOC__, builtinDoc);
9091
BoundBuiltinCallable<?> callable = function;
9192
if (builtin.isGetter() || builtin.isSetter()) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,27 +2480,26 @@ private void addDictDescrAttribute(PythonAbstractClass[] basesArray, PythonClass
24802480
// Note: we need to avoid MRO lookup of __dict__ using slots because they are not
24812481
// initialized yet
24822482
if ((!hasPythonClassBases(basesArray) && LookupAttributeInMRONode.lookupSlowPath(pythonClass, __DICT__) == PNone.NO_VALUE) || basesHaveSlots(basesArray)) {
2483+
Builtin dictBuiltin = ObjectBuiltins.DictNode.class.getAnnotation(Builtin.class);
24832484
RootCallTarget callTarget = PythonLanguage.getCurrent().createCachedCallTarget(
2484-
l -> {
2485-
Builtin dictBuiltin = ObjectBuiltins.DictNode.class.getAnnotation(Builtin.class);
2486-
return new BuiltinFunctionRootNode(l, dictBuiltin, new StandaloneBuiltinFactory<PythonBinaryBuiltinNode>(DictNodeGen.create()), true);
2487-
}, ObjectBuiltins.DictNode.class, StandaloneBuiltinFactory.class);
2488-
setAttribute(__DICT__, callTarget, pythonClass);
2485+
l -> new BuiltinFunctionRootNode(l, dictBuiltin, new StandaloneBuiltinFactory<PythonBinaryBuiltinNode>(DictNodeGen.create()), true), ObjectBuiltins.DictNode.class,
2486+
StandaloneBuiltinFactory.class);
2487+
setAttribute(__DICT__, dictBuiltin, callTarget, pythonClass);
24892488
}
24902489
}
24912490

24922491
@TruffleBoundary
24932492
private void addWeakrefDescrAttribute(PythonClass pythonClass) {
2493+
Builtin builtin = GetWeakRefsNode.class.getAnnotation(Builtin.class);
24942494
RootCallTarget callTarget = PythonLanguage.getCurrent().createCachedCallTarget(
2495-
l -> {
2496-
Builtin builtin = GetWeakRefsNode.class.getAnnotation(Builtin.class);
2497-
return new BuiltinFunctionRootNode(l, builtin, WeakRefModuleBuiltinsFactory.GetWeakRefsNodeFactory.getInstance(), true);
2498-
}, GetWeakRefsNode.class, WeakRefModuleBuiltinsFactory.class);
2499-
setAttribute(__WEAKREF__, callTarget, pythonClass);
2495+
l -> new BuiltinFunctionRootNode(l, builtin, WeakRefModuleBuiltinsFactory.GetWeakRefsNodeFactory.getInstance(), true), GetWeakRefsNode.class,
2496+
WeakRefModuleBuiltinsFactory.class);
2497+
setAttribute(__WEAKREF__, builtin, callTarget, pythonClass);
25002498
}
25012499

2502-
private void setAttribute(String name, RootCallTarget callTarget, PythonClass pythonClass) {
2503-
PBuiltinFunction function = factory().createBuiltinFunction(name, pythonClass, 1, callTarget);
2500+
private void setAttribute(String name, Builtin builtin, RootCallTarget callTarget, PythonClass pythonClass) {
2501+
int flags = PBuiltinFunction.getFlags(builtin, callTarget);
2502+
PBuiltinFunction function = factory().createBuiltinFunction(name, pythonClass, 1, flags, callTarget);
25042503
GetSetDescriptor desc = factory().createGetSetDescriptor(function, function, name, pythonClass, true);
25052504
pythonClass.setAttribute(name, desc);
25062505
}

0 commit comments

Comments
 (0)