aboutsummaryrefslogtreecommitdiff
blob: aa41df56ad18236f38a5af15a9e3aa61239eeb84 (plain)
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import py, os, sys
from rpython.jit.metainterp.test.support import LLJitMixin
from rpython.rlib.objectmodel import specialize, instantiate
from rpython.rlib import rarithmetic, rbigint, jit
from rpython.rtyper.lltypesystem import rffi, lltype
from rpython.rtyper import llinterp
from pypy.interpreter.baseobjspace import InternalSpaceCache, W_Root

from pypy.module.cppyy import interp_cppyy, capi, executor
# These tests are for the backend that support the fast path only.
if capi.identify() == 'CINT':
    py.test.skip("CINT does not support fast path")
elif capi.identify() == 'loadable_capi':
    py.test.skip("can not currently use FakeSpace with _cffi_backend")
elif os.getenv("CPPYY_DISABLE_FASTPATH"):
    py.test.skip("fast path is disabled by CPPYY_DISABLE_FASTPATH envar")

# load cpyext early, or its global vars are counted as leaks in the test
# (note that the module is not otherwise used in the test itself)
import pypy.module.cpyext

# change capi's direct_ptradd and exchange_address to being jit-opaque
@jit.dont_look_inside
def _opaque_direct_ptradd(ptr, offset):
    address = rffi.cast(rffi.CCHARP, ptr)
    return rffi.cast(capi.C_OBJECT, lltype.direct_ptradd(address, offset))
capi.direct_ptradd = _opaque_direct_ptradd

@jit.dont_look_inside
def _opaque_exchange_address(ptr, cif_descr, index):
    offset = rffi.cast(rffi.LONG, cif_descr.exchange_args[index])
    return rffi.ptradd(ptr, offset)
capi.exchange_address = _opaque_exchange_address

# add missing alt_errno (??)
def get_tlobj(self):
    try:
        return self._tlobj
    except AttributeError:
        from rpython.rtyper.lltypesystem import rffi
        PERRNO = rffi.CArrayPtr(rffi.INT)
        fake_p_errno = lltype.malloc(PERRNO.TO, 1, flavor='raw', zero=True,
                                     track_allocation=False)
        self._tlobj = {'RPY_TLOFS_p_errno': fake_p_errno,
                       'RPY_TLOFS_alt_errno': rffi.cast(rffi.INT, 0),
                       #'thread_ident': ...,
                       }
        return self._tlobj
llinterp.LLInterpreter.get_tlobj = get_tlobj


currpath = py.path.local(__file__).dirpath()
test_dct = str(currpath.join("example01Dict.so"))

def setup_module(mod):
    if sys.platform == 'win32':
        py.test.skip("win32 not supported so far")
    err = os.system("cd '%s' && make example01Dict.so" % currpath)
    if err:
        raise OSError("'make' failed (see stderr)")


class FakeBase(W_Root):
    typename = None

class FakeBool(FakeBase):
    typename = "bool"
    def __init__(self, val):
        self.val = val
class FakeInt(FakeBase):
    typename = "int"
    def __init__(self, val):
        self.val = val
class FakeLong(FakeBase):
    typename = "long"
    def __init__(self, val):
        self.val = val
class FakeFloat(FakeBase):
    typename = "float"
    def __init__(self, val):
        self.val = val
class FakeString(FakeBase):
    typename = "str"
    def __init__(self, val):
        self.val = val
class FakeType(FakeBase):
    typename = "type"
    def __init__(self, name):
        self.name = name
        self.__name__ = name
    def getname(self, space, name):
        return self.name
class FakeBuffer(FakeBase):
    typedname = "buffer"
    def __init__(self, val):
        self.val = val
    def get_raw_address(self):
        raise ValueError("no raw buffer")
class FakeException(FakeType):
    def __init__(self, space, name):
        FakeType.__init__(self, name)
        self.msg = name
        self.space = space

class FakeUserDelAction(object):
    def __init__(self, space):
        pass

    def register_callback(self, w_obj, callback, descrname):
        pass

    def perform(self, executioncontext, frame):
        pass

class FakeState(object):
    def __init__(self, space):
        self.slowcalls = 0

class FakeSpace(object):
    fake = True

    w_None = None
    w_str = FakeType("str")
    w_int = FakeType("int")
    w_float = FakeType("float")

    def __init__(self):
        self.fromcache = InternalSpaceCache(self).getorbuild
        self.user_del_action = FakeUserDelAction(self)
        class dummy: pass
        self.config = dummy()
        self.config.translating = False

        # kill calls to c_call_i (i.e. slow path)
        def c_call_i(space, cppmethod, cppobject, nargs, args):
            assert not "slow path called"
            return capi.c_call_i(space, cppmethod, cppobject, nargs, args)
        executor.get_executor(self, 'int').__class__.c_stubcall = staticmethod(c_call_i)

        self.w_AttributeError      = FakeException(self, "AttributeError")
        self.w_KeyError            = FakeException(self, "KeyError")
        self.w_NotImplementedError = FakeException(self, "NotImplementedError")
        self.w_ReferenceError      = FakeException(self, "ReferenceError")
        self.w_RuntimeError        = FakeException(self, "RuntimeError")
        self.w_SystemError         = FakeException(self, "SystemError")
        self.w_TypeError           = FakeException(self, "TypeError")
        self.w_ValueError          = FakeException(self, "ValueError")

    def issequence_w(self, w_obj):
        return True

    def wrap(self, obj):
        assert 0

    @specialize.argtype(1)
    def newbool(self, obj):
        return FakeBool(obj)

    @specialize.argtype(1)
    def newint(self, obj):
        if not isinstance(obj, int):
            return FakeLong(rbigint.rbigint.fromrarith_int(obj))
        return FakeInt(obj)

    @specialize.argtype(1)
    def newlong(self, obj):
        return FakeLong(rbigint.rbigint.fromint(obj))

    @specialize.argtype(1)
    def newlong_from_rarith_int(self, obj):
        return FakeLong(rbigint.rbigint.fromrarith_int(obj))

    def newlong_from_rbigint(self, val):
        return FakeLong(obj)

    @specialize.argtype(1)
    def newfloat(self, obj):
        return FakeFloat(obj)

    @specialize.argtype(1)
    def newbytes(self, obj):
        return FakeString(obj)

    @specialize.argtype(1)
    def newtext(self, obj):
        return FakeString(obj)

    def float_w(self, w_obj, allow_conversion=True):
        assert isinstance(w_obj, FakeFloat)
        return w_obj.val

    @specialize.arg(1)
    def interp_w(self, RequiredClass, w_obj, can_be_None=False):
        if can_be_None and w_obj is None:
            return None
        if not isinstance(w_obj, RequiredClass):
            raise TypeError
        return w_obj

    def getarg_w(self, code, w_obj):    # for retrieving buffers
        return FakeBuffer(w_obj)

    def exception_match(self, typ, sub):
        return typ is sub

    def is_w(self, w_one, w_two):
        return w_one is w_two

    def int_w(self, w_obj, allow_conversion=True):
        assert isinstance(w_obj, FakeInt)
        return w_obj.val

    def uint_w(self, w_obj):
        assert isinstance(w_obj, FakeLong)
        return rarithmetic.r_uint(w_obj.val.touint())

    def str_w(self, w_obj):
        assert isinstance(w_obj, FakeString)
        return w_obj.val

    def str(self, obj):
        assert isinstance(obj, str)
        return obj

    c_int_w = int_w
    r_longlong_w = int_w
    r_ulonglong_w = uint_w

    def is_(self, w_obj1, w_obj2):
        return w_obj1 is w_obj2

    def isinstance_w(self, w_obj, w_type):
        assert isinstance(w_obj, FakeBase)
        return w_obj.typename == w_type.name

    def is_true(self, w_obj):
        return not not w_obj

    def type(self, w_obj):
        return FakeType("fake")

    def getattr(self, w_obj, w_name):
        assert isinstance(w_obj, FakeException)
        assert self.str_w(w_name) == "__name__"
        return FakeString(w_obj.name)

    def findattr(self, w_obj, w_name):
        return None

    def allocate_instance(self, cls, w_type):
        return instantiate(cls)

    def call_function(self, w_func, *args_w):
        return None

    def _freeze_(self):
        return True

class TestFastPathJIT(LLJitMixin):
    def _run_zjit(self, method_name):
        space = FakeSpace()
        drv = jit.JitDriver(greens=[], reds=["i", "inst", "cppmethod"])
        def f():
            lib = interp_cppyy.load_dictionary(space, "./example01Dict.so")
            cls  = interp_cppyy.scope_byname(space, "example01")
            inst = cls.get_overload("example01").call(None, [FakeInt(0)])
            cppmethod = cls.get_overload(method_name)
            assert isinstance(inst, interp_cppyy.W_CPPInstance)
            i = 10
            while i > 0:
                drv.jit_merge_point(inst=inst, cppmethod=cppmethod, i=i)
                cppmethod.call(inst, [FakeInt(i)])
                i -= 1
            return 7
        f()
        space = FakeSpace()
        result = self.meta_interp(f, [], listops=True, backendopt=True, listcomp=True)
        self.check_jitcell_token_count(1)   # same for fast and slow path??
        # rely on replacement of capi calls to raise exception instead (see FakeSpace.__init__)

    def test01_simple(self):
        """Test fast path being taken for methods"""

        self._run_zjit("addDataToInt")

    def test02_overload(self):
        """Test fast path being taken for overloaded methods"""

        self._run_zjit("overloadedAddDataToInt")

    def test03_const_ref(self):
        """Test fast path being taken for methods with const ref arguments"""

        self._run_zjit("addDataToIntConstRef")