Skip to content

Commit c8565d5

Browse files
authored
Merge pull request #131 from Distributive-Network/Xmader/perf/return-underlying-function
unwrap functions when returned to their original language
2 parents 7e2c6be + dd7126f commit c8565d5

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

include/pyTypeFactory.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *thisObj, JS::Rooted
4444
* @param args - Pointer to a PyTupleObject containing the arguments to the python function
4545
* @return PyObject* - The result of the JSFunction called with args coerced to JS types, coerced back to a PyObject type, or NULL if coercion wasn't possible
4646
*/
47-
static PyObject *callJSFunc(PyObject *JSFuncAddress, PyObject *args);
47+
PyObject *callJSFunc(PyObject *JSFuncAddress, PyObject *args);
4848

4949
#endif

src/jsTypeFactory.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) {
117117
}
118118
memoizePyTypeAndGCThing(new StrType(object), returnType);
119119
}
120+
else if (PyCFunction_Check(object) && PyCFunction_GetFunction(object) == callJSFunc) {
121+
// If it's a wrapped JS function by us, return the underlying JS function rather than wrapping it again
122+
PyObject *jsCxThisFuncTuple = PyCFunction_GetSelf(object);
123+
JS::RootedValue *jsFunc = (JS::RootedValue *)PyLong_AsVoidPtr(PyTuple_GetItem(jsCxThisFuncTuple, 2));
124+
returnType.set(*jsFunc);
125+
}
120126
else if (PyFunction_Check(object) || PyCFunction_Check(object)) {
121127
// can't determine number of arguments for PyCFunctions, so just assume potentially unbounded
122128
uint16_t nargs = 0;

src/pyTypeFactory.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "include/modules/pythonmonkey/pythonmonkey.hh"
3333

3434
#include <jsapi.h>
35+
#include <jsfriendapi.h>
3536
#include <js/Object.h>
3637
#include <js/ValueArray.h>
3738

@@ -123,9 +124,16 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *thisObj, JS::Rooted
123124
return new ExceptionType(cx, obj);
124125
}
125126
case js::ESClass::Function: {
126-
// FIXME (Tom Tang): `jsCxThisFuncTuple` and the tuple items are not going to be GCed
127-
PyObject *jsCxThisFuncTuple = PyTuple_Pack(3, PyLong_FromVoidPtr(cx), PyLong_FromVoidPtr(thisObj), PyLong_FromVoidPtr(rval));
128-
PyObject *pyFunc = PyCFunction_New(&callJSFuncDef, jsCxThisFuncTuple);
127+
PyObject *pyFunc;
128+
if (JS_IsNativeFunction(obj, callPyFunc)) { // It's a wrapped python function by us
129+
// Get the underlying python function from the 0th reserved slot
130+
JS::Value pyFuncVal = js::GetFunctionNativeReserved(obj, 0);
131+
pyFunc = (PyObject *)(pyFuncVal.toPrivate());
132+
} else {
133+
// FIXME (Tom Tang): `jsCxThisFuncTuple` and the tuple items are not going to be GCed
134+
PyObject *jsCxThisFuncTuple = PyTuple_Pack(3, PyLong_FromVoidPtr(cx), PyLong_FromVoidPtr(thisObj), PyLong_FromVoidPtr(rval));
135+
pyFunc = PyCFunction_New(&callJSFuncDef, jsCxThisFuncTuple);
136+
}
129137
FuncType *f = new FuncType(pyFunc);
130138
memoizePyTypeAndGCThing(f, *rval); // TODO (Caleb Aikens) consider putting this in the FuncType constructor
131139
return f;
@@ -167,7 +175,7 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *thisObj, JS::Rooted
167175
return NULL;
168176
}
169177

170-
static PyObject *callJSFunc(PyObject *jsCxThisFuncTuple, PyObject *args) {
178+
PyObject *callJSFunc(PyObject *jsCxThisFuncTuple, PyObject *args) {
171179
// TODO (Caleb Aikens) convert PyObject *args to JS::Rooted<JS::ValueArray> JSargs
172180
JSContext *cx = (JSContext *)PyLong_AsVoidPtr(PyTuple_GetItem(jsCxThisFuncTuple, 0));
173181
JS::RootedObject *thisObj = (JS::RootedObject *)PyLong_AsVoidPtr(PyTuple_GetItem(jsCxThisFuncTuple, 1));

tests/python/test_pythonmonkey_eval.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,29 @@ def ident(x):
235235
# pm.collect() # TODO: to be fixed in BF-59
236236
assert "YYZ" == js_fn_back()
237237

238+
def test_eval_functions_pyfunction_in_closure():
239+
# BF-58 https://github.com/Distributive-Network/PythonMonkey/pull/19
240+
def fn1():
241+
def fn0(n):
242+
return n + 100
243+
return fn0
244+
assert 101.9 == fn1()(1.9)
245+
assert 101.9 == pm.eval("(fn1) => { return fn1 }")(fn1())(1.9)
246+
assert 101.9 == pm.eval("(fn1, x) => { return fn1()(x) }")(fn1, 1.9)
247+
assert 101.9 == pm.eval("(fn1) => { return fn1() }")(fn1)(1.9)
248+
249+
def test_unwrap_py_function():
250+
# https://github.com/Distributive-Network/PythonMonkey/issues/65
251+
def pyFunc():
252+
pass
253+
unwrappedPyFunc = pm.eval("(wrappedPyFunc) => { return wrappedPyFunc }")(pyFunc)
254+
assert unwrappedPyFunc is pyFunc
255+
256+
def test_unwrap_js_function():
257+
# https://github.com/Distributive-Network/PythonMonkey/issues/65
258+
wrappedJSFunc = pm.eval("const JSFunc = () => { return 0 }\nJSFunc")
259+
assert pm.eval("(unwrappedJSFunc) => { return unwrappedJSFunc === JSFunc }")(wrappedJSFunc)
260+
238261
def test_eval_functions_pyfunctions_ints():
239262
caller = pm.eval("(func, param1, param2) => { return func(param1, param2) }")
240263
def add(a, b):

0 commit comments

Comments
 (0)