File: apiset.py

package info (click to toggle)
pypy3 7.3.19%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 212,236 kB
  • sloc: python: 2,098,316; ansic: 540,565; sh: 21,462; asm: 14,419; cpp: 4,451; makefile: 4,209; objc: 761; xml: 530; exp: 499; javascript: 314; pascal: 244; lisp: 45; csh: 12; awk: 4
file content (233 lines) | stat: -rw-r--r-- 9,540 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import re
from rpython.translator import exceptiontransform
from rpython.rtyper.annlowlevel import llhelper
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.tool.sourcetools import func_with_new_name
from rpython.rlib.unroll import unrolling_iterable
from rpython.rlib.objectmodel import specialize
from rpython.rlib.debug import fatalerror_notb
from pypy.interpreter.error import OperationError
from pypy.module._hpy_universal import llapi

def _restore_gil_state(gil_release, _gil_auto):
    from rpython.rlib import rgil
    # see "Handling of the GIL" above
    unlock = gil_release or _gil_auto
    if unlock:
        rgil.release()

def deadlock_error(funcname):
    fatalerror_notb("GIL deadlock detected when a CPython C extension "
                    "module calls '%s'" % (funcname,))

def no_gil_error(funcname):
    fatalerror_notb("GIL not held when a CPython C extension "
                    "module calls '%s'" % (funcname,))


class APISet(object):

    def __init__(self, cts, mode, prefix=r'^_?HPy_?', force_c_name=False):
        self.cts = cts
        self.mode = mode
        self.prefix = re.compile(prefix)
        self.force_c_name = force_c_name
        self.all_functions = []
        self.frozen = False

    def _freeze_(self):
        self.all_functions = unrolling_iterable(self.all_functions)
        self.frozen = True
        return True

    def parse_signature(self, cdecl, error_value):
        d = self.cts.parse_func(cdecl)
        ARGS = d.get_llargs(self.cts)
        RESULT = d.get_llresult(self.cts)
        FUNC = lltype.Ptr(lltype.FuncType(ARGS, RESULT))
        return d.name, FUNC, self.get_ll_errval(d, FUNC, error_value)

    def get_ll_errval(self, d, FUNC, error_value):
        c_result_t = d.tp.result.get_c_name() # a string such as "HPy" or "void"
        if error_value is None:
            # automatically determine the error value from the return type
            if c_result_t == 'HPy':
                return 0
            elif c_result_t == 'void':
                return None
            elif isinstance(FUNC.TO.RESULT, lltype.Ptr):
                return lltype.nullptr(FUNC.TO.RESULT.TO)
            else:
                raise Exception(
                    "API function %s: you must explicitly specify an error_value "
                    "for functions returning %s" % (d.name, c_result_t))
        elif error_value == 'CANNOT_FAIL':
            # we need to specify an error_value anyway, let's just use the
            # exceptiontransform default
            return exceptiontransform.default_error_value(FUNC.TO.RESULT)
        else:
            assert c_result_t != 'HPy' # sanity check
            if lltype.typeOf(error_value) != FUNC.TO.RESULT:
                raise Exception(
                    "API function %s: the specified error_value has the "
                    "wrong lltype: expected %s, got %s" % (d.name, FUNC.TO.RESULT,
                                                           lltype.typeOf(error_value)))
            return error_value


    def func(self, cdecl, cpyext=False, func_name=None, error_value=None,
             is_helper=False, gil=None):
        """
        Declare an HPy API function.

        If the function is marked as cpyext=True, it will be included in the
        translation only if pypy.objspace.hpy_cpyext_API==True (the
        default). This is useful to exclude cpyext in test_ztranslation

        If func_name is given, the decorated function will be automatically
        renamed. Useful for automatically generated code, for example in
        interp_number.py

        error_value specifies the C value to return in case the function
        raises an RPython exception. The default behavior tries to be smart
        enough to work in the most common and standardized cases, but without
        guessing in case of doubts.  In particular, there is no default
        error_value for "int" functions, because CPython's behavior is not
        consistent.

        error_value can be:

            - None (the default): automatically determine the error value. It
              works only for the following return types:
                  * HPy: 0
                  * void: None
                  * pointers: NULL

            - 'CANNOT_FAIL': special string to specify that this function is
              not supposed to fail.

            - a specific value: in this case, the lltype must exactly match
              what is specified for the function type.

        is_helper=True is for functions which are not in the ctx. Useful if
        you need a ll_helper with a specific C signature, for example to use
        as a C callback.

        gil is for handling the PyPy GIL before and after the call. This is
        currently a subset of the cpyext handling, it may expand in the future.

        gil can be:

            - None (the default): the GIL should be held, If not held, acquire
              it before the call and release it after the call. This is useful
              when using HPy in C++/DLL initialization functions before proper
              interpreter startup.
            - "acquire": deadlock if the GIL is not currently held, and acquire
              it before the call. Do nothing after the call (continue holding it).
            - "release": do nothing in the call, release the GIL after the call
        """
        from rpython.rlib import rgil
        if self.frozen:
            raise RuntimeError(
                'Too late to call @api.func(), the API object has already been frozen. '
                'If you are calling @api.func() to decorate module-level functions, '
                'you might solve this by making sure that the module is imported '
                'earlier')
        gil_auto_workaround = (gil is None)  # automatically detect when we don't
                                             # have the GIL, and acquire/release it
        gil_acquire = (gil == "acquire")
        gil_release = (gil == "release")
        assert (gil is None or gil_acquire or gil_release)
        
        def decorate(fn):
            from pypy.module._hpy_universal.state import State
            name, ll_functype, ll_errval = self.parse_signature(cdecl, error_value)
            if name != fn.__name__:
                raise ValueError(
                    'The name of the function and the signature do not match: '
                    '%s != %s' % (name, fn.__name__))
            #
            if func_name is not None:
                fn = func_with_new_name(fn, func_name)
            #
            # attach various helpers to fn, so you can access things like
            # HPyNumber_Add.get_llhelper(), HPyNumber_Add.basename, etc.

            # get_llhelper
            @specialize.memo()
            def make_wrapper(space):
                def wrapper(*args):
                    _gil_auto = False
                    if gil_auto_workaround and not rgil.am_I_holding_the_GIL():
                        _gil_auto = True
                    if _gil_auto or gil_acquire:
                        if gil_acquire and rgil.am_I_holding_the_GIL():
                            deadlock_error(fn.__name__)
                        rgil.acquire()
                    else:
                        if not rgil.am_I_holding_the_GIL():
                            no_gil_error(fn.__name__)
                    state = space.fromcache(State)
                    if self.mode == "debug":
                        mode = llapi.MODE_DEBUG
                    elif self.mode == "trace":
                        mode = llapi.MODE_TRACE
                    else:
                        mode = llapi.MODE_UNIVERSAL
                    handles = state.get_handle_manager(mode)
                    try:
                        retval = fn(space, handles, *args)
                    except OperationError as e:
                        _restore_gil_state(gil_release, _gil_auto)
                        state.set_exception(e)
                        return ll_errval
                    _restore_gil_state(gil_release, _gil_auto)
                    return retval
                wrapper.__name__ = 'ctx_%s' % fn.__name__
                if self.force_c_name:
                    wrapper.c_name = fn.__name__
                return wrapper
            def get_llhelper(space):
                return llhelper(ll_functype, make_wrapper(space))
            get_llhelper.__name__ = 'get_llhelper_%s' % fn.__name__
            fn.get_llhelper = get_llhelper

            # basename
            fn.basename = self.prefix.sub(r'', fn.__name__)

            fn.cpyext = cpyext
            fn.is_helper = is_helper
            # record it into the API
            self.all_functions.append(fn)
            return fn
        return decorate

    @staticmethod
    def int(x):
        """
        Helper method to convert an RPython Signed into a C int
        """
        return rffi.cast(rffi.INT_real, x)

    @staticmethod
    def cast(typename, x):
        """
        Helper method to convert an RPython value into the correct C return
        type.
        """
        lltype = llapi.cts.gettype(typename)
        return rffi.cast(lltype, x)

    @staticmethod
    def ccharp2text(space, ptr):
        """
        Convert a C const char* into a W_UnicodeObject
        """
        s = rffi.constcharp2str(ptr)
        return space.newtext(s)



API = APISet(llapi.cts, mode="universal")
DEBUG = APISet(llapi.cts, mode="debug")
TRACE = APISet(llapi.cts, mode="trace")