Skip to content

Commit efaace2

Browse files
committed
Intrinsify PyObject_Call and reduce argument conversion overhead.
1 parent 0ad907d commit efaace2

6 files changed

Lines changed: 137 additions & 21 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,11 @@ inline void* native_to_java(PyObject* obj) {
205205
}
206206

207207
__attribute__((always_inline))
208-
inline PyObject* native_to_java_slim(PyObject* type) {
209-
if (!truffle_cannot_be_handle(type)) {
210-
return (PyObject *)truffle_managed_from_handle(type);
208+
inline void* native_to_java_slim(PyObject* obj) {
209+
if (!truffle_cannot_be_handle(obj)) {
210+
return truffle_managed_from_handle(obj);
211211
}
212-
return type;
212+
return obj;
213213
}
214214

215215
__attribute__((always_inline))

‎graalpython/com.oracle.graal.python.cext/src/object.c‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,8 @@ PyObject* PyObject_Repr(PyObject* o) {
186186
return UPCALL_CEXT_O(_jls_PyObject_Repr, native_to_java(o));
187187
}
188188

189-
UPCALL_ID(PyObject_Call);
190189
PyObject* PyObject_Call(PyObject* callable, PyObject* args, PyObject* kwargs) {
191-
if (kwargs == NULL) {
192-
kwargs = PyDict_New();
193-
}
194-
return UPCALL_CEXT_O(_jls_PyObject_Call, native_to_java(callable), native_to_java(args), native_to_java(kwargs));
190+
return polyglot_invoke(PY_TRUFFLE_CEXT, "PyObject_Call", native_to_java_slim(callable), native_to_java_slim(args), native_to_java_slim(kwargs));
195191
}
196192

197193
PyObject* PyObject_CallObject(PyObject* callable, PyObject* args) {
@@ -309,10 +305,7 @@ PyObject * _PyObject_FastCallDict(PyObject *func, PyObject *const *args, Py_ssiz
309305
for(i=0; i < nargs; i++) {
310306
PyTuple_SetItem(targs, i, args[i]);
311307
}
312-
if (kwargs == NULL) {
313-
kwargs = PyDict_New();
314-
}
315-
return UPCALL_CEXT_O(_jls_PyObject_Call, native_to_java(func), native_to_java(targs), native_to_java(kwargs));
308+
return polyglot_invoke(PY_TRUFFLE_CEXT, "PyObject_Call", native_to_java_slim(func), native_to_java_slim(targs), native_to_java_slim(kwargs));
316309
}
317310

318311
PyObject* PyObject_Type(PyObject* obj) {

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ def __call__(self, *args):
5252
cur = self.idx
5353
self.idx += 1
5454
return cur
55-
55+
56+
57+
def kw_fun(a, b=0, c=0):
58+
return {"a": a, "b": b, "c": c}
59+
60+
61+
def kwonly_fun(**kwargs):
62+
return kwargs
63+
5664

5765
class TestPyObject(CPyExtTestCase):
5866

@@ -112,17 +120,32 @@ def forgiving_len(o):
112120
arguments=["PyObject* op", "PyTypeObject* type"],
113121
resultspec="i",
114122
)
123+
115124
__PyObject_Call_ARGS = (
116125
(len, ((1, 2, 3),), {}),
117126
(sum, ((0, 1, 2),), {}),
118127
(format, (object(),), {"format_spec": ""}),
128+
(sum, ("hello, world",), {}),
129+
(kw_fun, (123,), {"c": 456, "b": 789}),
130+
(kwonly_fun, tuple(), {"x": 456, "y": 789}),
131+
(sum, ("hello, world",), None),
132+
(kwonly_fun, tuple(), None),
119133
)
120134

121135
test_PyObject_Call = CPyExtFunction(
122-
lambda args: args[0](*args[1], **args[2]),
136+
lambda args: args[0](*args[1], **args[2]) if args[2] else args[0](*args[1], **dict()),
123137
lambda: TestPyObject.__PyObject_Call_ARGS,
138+
code='''#include <stdio.h>
139+
PyObject * wrap_PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs) {
140+
if(kwargs == Py_None) {
141+
return PyObject_Call(callable, args, NULL);
142+
}
143+
return PyObject_Call(callable, args, kwargs);
144+
}
145+
''',
124146
arguments=["PyObject* callable", "PyObject* callargs", "PyObject* kwargs"],
125147
argspec="OOO",
148+
callfunction="wrap_PyObject_Call",
126149
)
127150
test_PyObject_CallObject = CPyExtFunction(
128151
lambda args: args[0](*args[1]),

‎graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,14 @@
145145
import com.oracle.graal.python.nodes.argument.ReadIndexedArgumentNode;
146146
import com.oracle.graal.python.nodes.argument.ReadVarArgsNode;
147147
import com.oracle.graal.python.nodes.argument.ReadVarKeywordsNode;
148+
import com.oracle.graal.python.nodes.argument.keywords.ExecuteKeywordStarargsNode.ExpandKeywordStarargsNode;
149+
import com.oracle.graal.python.nodes.argument.positional.ExecutePositionalStarargsNode;
148150
import com.oracle.graal.python.nodes.attributes.HasInheritedAttributeNode;
149151
import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode;
150152
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNode;
151153
import com.oracle.graal.python.nodes.attributes.WriteAttributeToDynamicObjectNode;
152154
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
155+
import com.oracle.graal.python.nodes.call.CallNode;
153156
import com.oracle.graal.python.nodes.call.InvokeNode;
154157
import com.oracle.graal.python.nodes.call.PythonCallNode;
155158
import com.oracle.graal.python.nodes.call.special.LookupAndCallBinaryNode;
@@ -199,6 +202,7 @@
199202
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
200203
import com.oracle.truffle.api.dsl.ImportStatic;
201204
import com.oracle.truffle.api.dsl.NodeFactory;
205+
import com.oracle.truffle.api.dsl.ReportPolymorphism;
202206
import com.oracle.truffle.api.dsl.Specialization;
203207
import com.oracle.truffle.api.dsl.TypeSystemReference;
204208
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -2768,4 +2772,104 @@ private CoderResult decode(CharBuffer resultBuffer, ByteBuffer inputBuffer, Stri
27682772
return decoder.onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(action).decode(inputBuffer, resultBuffer, true);
27692773
}
27702774
}
2775+
2776+
@Builtin(name = "PyObject_Call", parameterNames = {"callee", "args", "kwargs"})
2777+
@GenerateNodeFactory
2778+
@ReportPolymorphism
2779+
abstract static class PyObjectCallNode extends PythonTernaryBuiltinNode {
2780+
2781+
@Specialization(guards = "kwargsLib.isNull(kwargsObj)")
2782+
Object doNoKeywords(VirtualFrame frame, Object callableObj, Object argsObj, @SuppressWarnings("unused") Object kwargsObj,
2783+
@CachedLibrary(limit = "3") @SuppressWarnings("unused") InteropLibrary kwargsLib,
2784+
@Shared("expandArgsNode") @Cached ExecutePositionalStarargsNode expandArgsNode,
2785+
@Shared("callableToJavaNode") @Cached CExtNodes.AsPythonObjectNode callableToJavaNode,
2786+
@Cached CExtNodes.AsPythonObjectNode argsToJavaNode,
2787+
@Shared("toSulongNode") @Cached CExtNodes.ToSulongNode toSulongNode,
2788+
@Shared("callNodeNoKeywords") @Cached CallNode callNode,
2789+
@Cached GetNativeNullNode getNativeNullNode,
2790+
@Shared("nullToSulongNode") @Cached CExtNodes.ToSulongNode nullToSulongNode) {
2791+
try {
2792+
Object callable = callableToJavaNode.execute(callableObj);
2793+
Object[] args = expandArgsNode.executeWith(frame, argsToJavaNode.execute(argsObj));
2794+
return toSulongNode.execute(callNode.execute(frame, callable, args, PKeyword.EMPTY_KEYWORDS));
2795+
} catch (PException e) {
2796+
// getContext() acts as a branch profile
2797+
NativeBuiltin.transformToNative(getContext(), PArguments.getCurrentFrameInfo(frame), e);
2798+
return nullToSulongNode.execute(getNativeNullNode.execute());
2799+
}
2800+
}
2801+
2802+
@Specialization(guards = {"!kwargsLib.isNull(kwargsObj)", "isEmptyDict(kwargsToJavaNode, lenNode, kwargsObj)"}, limit = "1")
2803+
Object doEmptyKeywords(VirtualFrame frame, Object callableObj, Object argsObj, @SuppressWarnings("unused") Object kwargsObj,
2804+
@CachedLibrary(limit = "3") @SuppressWarnings("unused") InteropLibrary kwargsLib,
2805+
@Shared("expandArgsNode") @Cached ExecutePositionalStarargsNode expandArgsNode,
2806+
@Shared("callableToJavaNode") @Cached CExtNodes.AsPythonObjectNode callableToJavaNode,
2807+
@Cached CExtNodes.AsPythonObjectNode argsToJavaNode,
2808+
@Cached CExtNodes.AsPythonObjectNode kwargsToJavaNode,
2809+
@Cached HashingCollectionNodes.LenNode lenNode,
2810+
@Shared("toSulongNode") @Cached CExtNodes.ToSulongNode toSulongNode,
2811+
@Shared("callNodeNoKeywords") @Cached CallNode callNode,
2812+
@Cached GetNativeNullNode getNativeNullNode,
2813+
@Shared("nullToSulongNode") @Cached CExtNodes.ToSulongNode nullToSulongNode) {
2814+
try {
2815+
Object callable = callableToJavaNode.execute(callableObj);
2816+
Object[] args = expandArgsNode.executeWith(frame, argsToJavaNode.execute(argsObj));
2817+
return toSulongNode.execute(callNode.execute(frame, callable, args, PKeyword.EMPTY_KEYWORDS));
2818+
} catch (PException e) {
2819+
// getContext() acts as a branch profile
2820+
NativeBuiltin.transformToNative(getContext(), PArguments.getCurrentFrameInfo(frame), e);
2821+
return nullToSulongNode.execute(getNativeNullNode.execute());
2822+
}
2823+
}
2824+
2825+
@Specialization(replaces = {"doNoKeywords", "doEmptyKeywords"})
2826+
Object doGeneric(VirtualFrame frame, Object callableObj, Object argsObj, Object kwargsObj,
2827+
@CachedLibrary(limit = "3") @SuppressWarnings("unused") InteropLibrary argsLib,
2828+
@CachedLibrary(limit = "3") @SuppressWarnings("unused") InteropLibrary kwargsLib,
2829+
@Shared("expandArgsNode") @Cached ExecutePositionalStarargsNode expandArgsNode,
2830+
@Cached ExpandKeywordStarargsNode expandKwargsNode,
2831+
@Shared("callableToJavaNode") @Cached CExtNodes.AsPythonObjectNode callableToJavaNode,
2832+
@Cached CExtNodes.AsPythonObjectNode argsToJavaNode,
2833+
@Cached CExtNodes.AsPythonObjectNode kwargsToJavaNode,
2834+
@Cached("createBinaryProfile()") ConditionProfile argsIsNullProfile,
2835+
@Cached("createBinaryProfile()") ConditionProfile kwargsIsNullProfile,
2836+
@Exclusive @Cached CallNode callNode,
2837+
@Shared("toSulongNode") @Cached CExtNodes.ToSulongNode toSulongNode,
2838+
@Cached GetNativeNullNode getNativeNullNode,
2839+
@Shared("nullToSulongNode") @Cached CExtNodes.ToSulongNode nullToSulongNode) {
2840+
try {
2841+
Object callable = callableToJavaNode.execute(callableObj);
2842+
Object[] args;
2843+
PKeyword[] keywords;
2844+
2845+
// expand positional arguments
2846+
if (argsIsNullProfile.profile(argsLib.isNull(argsObj))) {
2847+
args = new Object[0];
2848+
} else {
2849+
args = expandArgsNode.executeWith(frame, argsToJavaNode.execute(argsObj));
2850+
}
2851+
2852+
// expand keywords
2853+
if (kwargsIsNullProfile.profile(kwargsLib.isNull(kwargsObj))) {
2854+
keywords = PKeyword.EMPTY_KEYWORDS;
2855+
} else {
2856+
keywords = expandKwargsNode.executeWith(kwargsToJavaNode.execute(kwargsObj));
2857+
}
2858+
return toSulongNode.execute(callNode.execute(frame, callable, args, keywords));
2859+
} catch (PException e) {
2860+
// getContext() acts as a branch profile
2861+
NativeBuiltin.transformToNative(getContext(), PArguments.getCurrentFrameInfo(frame), e);
2862+
return nullToSulongNode.execute(getNativeNullNode.execute());
2863+
}
2864+
}
2865+
2866+
static boolean isEmptyDict(CExtNodes.AsPythonObjectNode asPythonObjectNode, HashingCollectionNodes.LenNode lenNode, Object kwargsObj) {
2867+
Object unwrapped = asPythonObjectNode.execute(kwargsObj);
2868+
if (unwrapped instanceof PDict) {
2869+
return lenNode.execute((PDict) unwrapped) == 0;
2870+
}
2871+
return false;
2872+
}
2873+
2874+
}
27712875
}

‎graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/CExtNodes.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ public static AllToSulongNode create() {
10911091
}
10921092

10931093
// -----------------------------------------------------------------------------------------------------------------
1094+
10941095
public abstract static class DirectUpcallNode extends PNodeWithContext {
10951096
public abstract Object execute(VirtualFrame frame, Object callable, Object[] args);
10961097

@@ -1659,7 +1660,7 @@ boolean doSpecial(PythonNativeWrapper obj,
16591660
return getSpecialSingletonPtrNode.execute(lib.getDelegate(obj)) != null;
16601661
}
16611662

1662-
@Specialization(limit = "1")
1663+
@Specialization(limit = "1", replaces = {"doNative", "doSpecial"})
16631664
boolean doGeneric(PythonNativeWrapper obj,
16641665
@CachedLibrary("obj") PythonNativeWrapperLibrary lib,
16651666
@Cached GetSpecialSingletonPtrNode getSpecialSingletonPtrNode,

‎graalpython/lib-graalpython/python_cext.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,11 +1061,6 @@ def PyObject_Size(obj):
10611061
return -1
10621062

10631063

1064-
@may_raise
1065-
def PyObject_Call(callee, args, kwargs):
1066-
return callee(*args, **kwargs)
1067-
1068-
10691064
@may_raise
10701065
def PyObject_CallMethod(rcvr, method, args):
10711066
# TODO(fa) that seems to be a workaround

0 commit comments

Comments
 (0)