import py, sys
from rpython.jit.codewriter import support
from rpython.jit.codewriter.heaptracker import int_signext
from rpython.jit.codewriter.flatten import flatten_graph, reorder_renaming_list
from rpython.jit.codewriter.flatten import GraphFlattener, ListOfKind, Register
from rpython.jit.codewriter.format import assert_format
from rpython.jit.codewriter import longlong
from rpython.jit.codewriter.effectinfo import EffectInfo
from rpython.jit.metainterp.history import AbstractDescr
from rpython.rtyper.lltypesystem import lltype, rstr, rffi
from rpython.rtyper import rclass
from rpython.flowspace.model import SpaceOperation, Variable, Constant
from rpython.translator.unsimplify import varoftype
from rpython.rlib.rarithmetic import ovfcheck, r_uint, r_longlong, r_ulonglong
from rpython.rlib.jit import dont_look_inside, _we_are_jitted, JitDriver
from rpython.rlib.objectmodel import keepalive_until_here
from rpython.rlib import jit


class FakeRegAlloc:
    # a RegAllocator that answers "0, 1, 2, 3, 4..." for the colors
    def __init__(self):
        self.seen = {}
        self.num_colors = 0
    def getcolor(self, v):
        if v not in self.seen:
            self.seen[v] = self.num_colors
            self.num_colors += 1
        return self.seen[v]

def fake_regallocs():
    return {'int': FakeRegAlloc(),
            'ref': FakeRegAlloc(),
            'float': FakeRegAlloc()}

class FakeDescr(AbstractDescr):
    _for_tests_only = True
    def __init__(self, oopspecindex=None):
        self.oopspecindex = oopspecindex
    def __repr__(self):
        return '<Descr>'

class FakeDict(object):
    def __getitem__(self, key):
        F = lltype.FuncType([lltype.Signed, lltype.Signed], lltype.Signed)
        f = lltype.functionptr(F, key[0])
        c_func = Constant(f, lltype.typeOf(f))
        return c_func, lltype.Signed

class FakeCPU:
    class tracker:
        pass

    def __init__(self, rtyper):
        rtyper._builtin_func_for_spec_cache = FakeDict()
        self.rtyper = rtyper
    def calldescrof(self, FUNC, ARGS, RESULT, effectinfo):
        return FakeDescr()
    def fielddescrof(self, STRUCT, name):
        return FakeDescr()
    def sizeof(self, STRUCT, vtable=None):
        return FakeDescr()
    def arraydescrof(self, ARRAY):
        return FakeDescr()

class FakeCallInfoCollection:
    def add(self, *args):
        pass

class FakeCallControl:
    _descr_cannot_raise = FakeDescr()
    callinfocollection = FakeCallInfoCollection()
    def guess_call_kind(self, op):
        return 'residual'
    def getcalldescr(self, op, oopspecindex=EffectInfo.OS_NONE,
                     extraeffect=None, extradescr=None):
        try:
            name = op.args[0].value._obj._name
            if 'cannot_raise' in name or name.startswith('cast_'):
                return self._descr_cannot_raise
        except AttributeError:
            pass
        return FakeDescr(oopspecindex)
    def calldescr_canraise(self, calldescr):
        return calldescr is not self._descr_cannot_raise and calldescr.oopspecindex == EffectInfo.OS_NONE
    def get_vinfo(self, VTYPEPTR):
        if hasattr(VTYPEPTR.TO, 'inst_vlist'):
            return FakeVInfo()
        return None

class FakeCallControlWithVRefInfo:
    class virtualref_info:
        JIT_VIRTUAL_REF = lltype.GcStruct('VREF', ('parent', rclass.OBJECT))
        jit_virtual_ref_vtable = lltype.malloc(rclass.OBJECT_VTABLE,
                                               immortal=True)
    def guess_call_kind(self, op):
        if op.args[0].value._obj._name == 'jit_force_virtual':
            return 'residual'
        return 'builtin'
    def getcalldescr(self, op, **kwds):
        return FakeDescr()
    def calldescr_canraise(self, calldescr):
        return False

class FakeVInfo:
    static_field_to_extra_box = {}
    array_fields = {'inst_vlist': '?'}
    array_field_counter = {'inst_vlist': 0}
    array_field_descrs = [FakeDescr()]
    array_descrs = [FakeDescr()]

# ____________________________________________________________

def test_reorder_renaming_list():
    result = reorder_renaming_list([], [])
    assert result == []
    result = reorder_renaming_list([1, 2, 3], [4, 5, 6])
    assert result == [(1, 4), (2, 5), (3, 6)]
    result = reorder_renaming_list([4, 5, 1, 2], [1, 2, 3, 4])
    assert result == [(1, 3), (4, 1), (2, 4), (5, 2)]
    result = reorder_renaming_list([1, 2], [2, 1])
    assert result == [(1, None), (2, 1), (None, 2)]
    result = reorder_renaming_list([4, 3, 6, 1, 2, 5, 7],
                                   [1, 2, 5, 3, 4, 6, 8])
    assert result == [(7, 8),
                      (4, None), (2, 4), (3, 2), (1, 3), (None, 1),
                      (6, None), (5, 6), (None, 5)]

def test_repr():
    assert repr(Register('int', 13)) == '%i13'

# ____________________________________________________________


class TestFlatten:

    def make_graphs(self, func, values):
        self.rtyper = support.annotate(func, values)
        return self.rtyper.annotator.translator.graphs

    def encoding_test(self, func, args, expected,
                      transform=False, liveness=False, cc=None, jd=None):
        
        graphs = self.make_graphs(func, args)
        #graphs[0].show()
        if transform:
            from rpython.jit.codewriter.jtransform import transform_graph
            cc = cc or FakeCallControl()
            transform_graph(graphs[0], FakeCPU(self.rtyper), cc, jd)
        ssarepr = flatten_graph(graphs[0], fake_regallocs(),
                                _include_all_exc_links=not transform,
                                cpu=FakeCPU(self.rtyper))
        if liveness:
            from rpython.jit.codewriter.liveness import compute_liveness
            compute_liveness(ssarepr)
        if expected is not None:
            assert_format(ssarepr, expected)

    def test_simple(self):
        def f(n):
            return n + 10
        self.encoding_test(f, [5], """
            int_add %i0, $10 -> %i1
            int_return %i1
        """)

    def test_if(self):
        def f(n):
            if n > 0:
                n -= 3
            return n + 1
        self.encoding_test(f, [10], """
            int_gt %i0, $0 -> %i1
            -live-
            goto_if_not %i1, L1
            int_copy %i0 -> %i2
            int_sub %i2, $3 -> %i3
            int_copy %i3 -> %i4
            L2:
            int_add %i4, $1 -> %i5
            int_return %i5
            ---
            L1:
            int_copy %i0 -> %i4
            goto L2
        """)

    def test_loop(self):
        def f(a, b):
            while a > 0:
                b += a
                a -= 1
            return b
        self.encoding_test(f, [5, 6], """
            int_copy %i0 -> %i2
            int_copy %i1 -> %i3
            L1:
            int_gt %i2, $0 -> %i4
            -live-
            goto_if_not %i4, L2
            int_copy %i2 -> %i5
            int_copy %i3 -> %i6
            int_add %i6, %i5 -> %i7
            int_sub %i5, $1 -> %i8
            int_copy %i8 -> %i2
            int_copy %i7 -> %i3
            goto L1
            ---
            L2:
            int_return %i3
        """)

    def test_loop_opt(self):
        def f(a, b):
            while a > 0:
                b += a
                a -= 1
            return b
        self.encoding_test(f, [5, 6], """
            int_copy %i0 -> %i2
            int_copy %i1 -> %i3
            L1:
            -live-
            goto_if_not_int_gt %i2, $0, L2
            int_copy %i2 -> %i4
            int_copy %i3 -> %i5
            int_add %i5, %i4 -> %i6
            int_sub %i4, $1 -> %i7
            int_copy %i7 -> %i2
            int_copy %i6 -> %i3
            goto L1
            ---
            L2:
            int_return %i3
        """, transform=True)

    def test_float(self):
        def f(i, f):
            return (i*5) + (f*0.25)
        self.encoding_test(f, [4, 7.5], """
            int_mul %i0, $5 -> %i1
            float_mul %f0, $0.25 -> %f1
            cast_int_to_float %i1 -> %f2
            float_add %f2, %f1 -> %f3
            float_return %f3
        """)

    def test_arg_sublist_1(self):
        v1 = varoftype(lltype.Signed)
        v2 = varoftype(lltype.Char)
        v3 = varoftype(rclass.OBJECTPTR)
        v4 = varoftype(lltype.Ptr(rstr.STR))
        v5 = varoftype(lltype.Float)
        op = SpaceOperation('residual_call_ir_f',
                            [Constant(12345, lltype.Signed),  # function ptr
                             ListOfKind('int', [v1, v2]),     # int args
                             ListOfKind('ref', [v3, v4])],    # ref args
                            v5)                    # result
        flattener = GraphFlattener(None, fake_regallocs())
        flattener.serialize_op(op)
        assert_format(flattener.ssarepr, """
            residual_call_ir_f $12345, I[%i0, %i1], R[%r0, %r1] -> %f0
        """)

    def test_same_as_removal(self):
        def f(a):
            b = chr(a)
            return ord(b) + a
        self.encoding_test(f, [65], """
            int_add %i0, %i0 -> %i1
            int_return %i1
        """, transform=True)

    def test_descr(self):
        class FooDescr(AbstractDescr):
            def __repr__(self):
                return 'hi_there!'
        op = SpaceOperation('foobar', [FooDescr()], None)
        flattener = GraphFlattener(None, fake_regallocs())
        flattener.serialize_op(op)
        assert_format(flattener.ssarepr, """
            foobar hi_there!
        """)

    def test_switch_dict(self):
        def f(x):
            if   x == 1: return 61
            elif x == 2: return 511
            elif x == 3: return -22
            elif x == 4: return 81
            elif x == 5: return 17
            elif x == 6: return 54
            return -1
        self.encoding_test(f, [65], """
            -live-
            switch %i0, <SwitchDictDescr 1:L1, 2:L2, 3:L3, 4:L4, 5:L5, 6:L6>
            int_return $-1
            ---
            L1:
            -live-
            int_return $61
            ---
            L2:
            -live-
            int_return $511
            ---
            L3:
            -live-
            int_return $-22
            ---
            L4:
            -live-
            int_return $81
            ---
            L5:
            -live-
            int_return $17
            ---
            L6:
            -live-
            int_return $54
        """)

    def test_switch_longlong(self):
        def f(n):
            n = r_longlong(n)
            if n == r_longlong(-5):  return 12
            elif n == r_longlong(2): return 51
            elif n == r_longlong(7): return 1212
            else:                    return 42
        self.encoding_test(f, [65], None)

    def test_exc_exitswitch(self):
        def g(i):
            pass

        def f(i):
            try:
                g(i)
            except ValueError:
                return 1
            except KeyError:
                return 2
            else:
                return 3

        self.encoding_test(f, [65], """
            direct_call $<* fn g>, %i0
            catch_exception L1
            int_return $3
            ---
            L1:
            goto_if_exception_mismatch $<* struct object_vtable>, L2
            int_return $1
            ---
            L2:
            goto_if_exception_mismatch $<* struct object_vtable>, L3
            int_return $2
            ---
            L3:
            reraise
        """)

    def test_exc_exitswitch_2(self):
        class FooError(Exception):
            pass
        @dont_look_inside
        def g(i):
            FooError().num = 1
            FooError().num = 2
        def f(i):
            try:
                g(i)
            except FooError as e:
                return e.num
            except Exception:
                return 3
            else:
                return 4

        self.encoding_test(f, [65], """
            residual_call_ir_v $<* fn g>, I[%i0], R[], <Descr>
            -live-
            catch_exception L1
            int_return $4
            ---
            L1:
            goto_if_exception_mismatch $<* struct object_vtable>, L2
            last_exc_value -> %r0
            getfield_gc_i %r0, <Descr> -> %i1
            int_return %i1
            ---
            L2:
            int_return $3
        """, transform=True)

    def test_exc_raise_1(self):
        class FooError(Exception):
            pass
        fooerror = FooError()
        def f(i):
            raise fooerror

        self.encoding_test(f, [65], """
        raise $<* struct object>
        """)

    def test_exc_raise_2(self):
        def g(i):
            pass
        def f(i):
            try:
                g(i)
            except Exception:
                raise KeyError

        self.encoding_test(f, [65], """
            direct_call $<* fn g>, %i0
            catch_exception L1
            void_return
            ---
            L1:
            raise $<* struct object>
        """)

    def test_exc_finally(self):
        def get_exception(n):
            if n > 5:
                raise ValueError
        class Foo:
            pass
        Foo.__module__ = "test"
        foo = Foo()
        def f(i):
            try:
                get_exception(i)
            finally:
                foo.sideeffect = 5

        self.encoding_test(f, [65], """
        residual_call_ir_v $<* fn get_exception>, I[%i0], R[], <Descr>
        -live-
        catch_exception L1
        setfield_gc_i $<* struct test.Foo>, $5, <Descr>
        void_return
        ---
        L1:
        last_exception -> %i1
        last_exc_value -> %r0
        setfield_gc_i $<* struct test.Foo>, $5, <Descr>
        -live-
        raise %r0
        """, transform=True)

    def test_goto_if_not_int_is_true(self):
        def f(i):
            return not i

        # note that 'goto_if_not_int_is_true' is not the same thing
        # as just 'goto_if_not', because the last one expects a boolean
        self.encoding_test(f, [7], """
            -live-
            goto_if_not_int_is_true %i0, L1
            int_return $False
            ---
            L1:
            int_return $True
        """, transform=True)

    def test_int_floordiv_ovf_zer(self):
        def f(i, j):
            assert i >= 0
            assert j >= 0
            try:
                return ovfcheck(i // j)
            except OverflowError:
                return 42
            except ZeroDivisionError:
                return -42
        self.encoding_test(f, [7, 2], """
            residual_call_ir_i $<* fn ll_int_floordiv_ovf_zer__Signed_Signed>, I[%i0, %i1], R[], <Descr> -> %i2
            -live-
            catch_exception L1
            int_return %i2
            ---
            L1:
            goto_if_exception_mismatch $<* struct object_vtable>, L2
            int_return $42
            ---
            L2:
            goto_if_exception_mismatch $<* struct object_vtable>, L3
            int_return $-42
            ---
            L3:
            reraise
        """, transform=True)

    def test_int_mod_ovf(self):
        def f(i, j):
            assert i >= 0
            assert j >= 0
            try:
                return ovfcheck(i % j)
            except OverflowError:
                return 42
        # XXX so far, this really produces a int_mod_ovf_zer...
        self.encoding_test(f, [7, 2], """
            residual_call_ir_i $<* fn ll_int_mod_ovf_zer__Signed_Signed>, I[%i0, %i1], R[], <Descr> -> %i2
            -live-
            catch_exception L1
            int_return %i2
            ---
            L1:
            goto_if_exception_mismatch $<* struct object_vtable>, L2
            int_return $42
            ---
            L2:
            reraise
        """, transform=True)

    def test_simple_branch(self):
        def f(n, m1, m2):
            if n:
                return m1
            else:
                return m2
        self.encoding_test(f, [4, 5, 6], """
            -live- %i0, %i1, %i2
            goto_if_not_int_is_true %i0, L1
            int_return %i1
            ---
            L1:
            int_return %i2
        """, transform=True, liveness=True)

    def test_int_add_ovf(self):
        def f(i, j):
            try:
                return ovfcheck(i + j)
            except OverflowError:
                return 42
        self.encoding_test(f, [7, 2], """
            -live- %i0, %i1
            int_add_jump_if_ovf L1, %i0, %i1 -> %i2
            int_return %i2
            ---
            L1:
            int_return $42
        """, transform=True, liveness=True)

    def test_int_sub_ovf(self):
        def f(i, j):
            try:
                return ovfcheck(i - j)
            except OverflowError:
                return 42
        self.encoding_test(f, [7, 2], """
            -live- %i0, %i1
            int_sub_jump_if_ovf L1, %i0, %i1 -> %i2
            int_return %i2
            ---
            L1:
            int_return $42
        """, transform=True, liveness=True)

    def test_int_mul_ovf(self):
        def f(i, j):
            try:
                return ovfcheck(i * j)
            except OverflowError:
                return 42
        self.encoding_test(f, [7, 2], """
            -live- %i0, %i1
            int_mul_jump_if_ovf L1, %i0, %i1 -> %i2
            int_return %i2
            ---
            L1:
            int_return $42
        """, transform=True, liveness=True)

    def test_multiple_int_add_ovf(self):
        def f(i, j):
            try:
                ovfcheck(j + i)
                return ovfcheck(i + j)
            except OverflowError:
                return 42
        self.encoding_test(f, [7, 2], """
            -live- %i0, %i1
            int_add_jump_if_ovf L1, %i1, %i0 -> %i2
            int_copy %i1 -> %i3
            int_copy %i0 -> %i4
            -live- %i3, %i4
            int_add_jump_if_ovf L2, %i4, %i3 -> %i5
            int_return %i5
            ---
            L2:
            int_return $42
            ---
            L1:
            int_return $42
        """, transform=True, liveness=True)

    def test_ovfcheck_no_catch(self):
        def f(i, j):
            return ovfcheck(i + j)
        err = py.test.raises(Exception, "self.encoding_test(f, [7, 2], '',"
                             "transform=True, liveness=True)")
        assert "ovfcheck()" in str(err.value)

    def test_ovfcheck_reraise(self):
        def f(i, j):
            try:
                ovfcheck(j + i)
            except OverflowError:
                raise
        self.encoding_test(f, [7, 2], """
            -live- %i0, %i1
            int_add_jump_if_ovf L1, %i1, %i0 -> %i2
            void_return
            ---
            L1:
            raise $<* struct object>
        """, transform=True, liveness=True)

    def test_residual_call_raising(self):
        @dont_look_inside
        def g(i, j):
            return ovfcheck(i + j)
        def f(i, j):
            try:
                return g(i, j)
            except Exception:
                return 42 + j
        self.encoding_test(f, [7, 2], """
            residual_call_ir_i $<* fn g>, I[%i0, %i1], R[], <Descr> -> %i2
            -live- %i1, %i2
            catch_exception L1
            int_return %i2
            ---
            L1:
            int_copy %i1 -> %i3
            int_add %i3, $42 -> %i4
            int_return %i4
        """, transform=True, liveness=True)

    def test_residual_call_nonraising(self):
        @dont_look_inside
        def cannot_raise(i, j):
            return i + j
        def f(i, j):
            try:
                return cannot_raise(i, j)
            except Exception:
                return 42 + j
        self.encoding_test(f, [7, 2], """
            residual_call_ir_i $<* fn cannot_raise>, I[%i0, %i1], R[], <Descr> -> %i2
            int_return %i2
        """, transform=True, liveness=True)

    def test_we_are_jitted(self):
        def f(x):
            if _we_are_jitted:
                return 2
            else:
                return 3 + x
        self.encoding_test(f, [5], """
            int_return $2
        """, transform=True)

    def test_jitdriver(self):
        myjitdriver = JitDriver(greens = ['x'], reds = ['y'])
        def f(x, y):
            myjitdriver.jit_merge_point(x=x, y=y)
            myjitdriver.can_enter_jit(x=y, y=x)
        class FakeJitDriverSD:
            jitdriver = myjitdriver
            index = 27
        jd = FakeJitDriverSD()
        class MyFakeCallControl(FakeCallControl):
            def jitdriver_sd_from_jitdriver(self, jitdriver):
                assert jitdriver == myjitdriver
                return jd
        self.encoding_test(f, [4, 5], """
            -live- %i0, %i1
            int_guard_value %i0
            -live- %i0, %i1
            jit_merge_point $27, I[%i0], R[], F[], I[%i1], R[], F[]
            -live-
            loop_header $27
            void_return
        """, transform=True, liveness=True, cc=MyFakeCallControl(), jd=jd)

    def test_keepalive(self):
        S = lltype.GcStruct('S')
        def g():
            return lltype.malloc(S)
        def f(x):
            p = g()
            q = g()
            keepalive_until_here(p)
            keepalive_until_here(q)
            return x
        self.encoding_test(f, [5], """
            residual_call_r_r $<* fn g>, R[], <Descr> -> %r0
            -live-
            residual_call_r_r $<* fn g>, R[], <Descr> -> %r1
            -live-
            -live- %r0
            -live- %r1
            int_return %i0
        """, transform=True)
        self.encoding_test(f, [5], """
            residual_call_r_r $<* fn g>, R[], <Descr> -> %r0
            -live- %i0, %r0
            residual_call_r_r $<* fn g>, R[], <Descr> -> %r1
            -live- %i0, %r0, %r1
            -live- %i0, %r0, %r1
            -live- %i0, %r1
            int_return %i0
        """, transform=True, liveness=True)

    def test_ptr_nonzero(self):
        def f(p):
            if p:
                return 12
            return 34
        S = lltype.GcStruct('S')
        self.encoding_test(f, [lltype.malloc(S)], """
            -live- %r0
            goto_if_not_ptr_nonzero %r0, L1
            int_return $12
            ---
            L1:
            int_return $34
        """, transform=True, liveness=True)

    def test_vref_simple(self):
        class X:
            pass
        def f():
            return jit.virtual_ref(X())
        self.encoding_test(f, [], """
            new_with_vtable <Descr> -> %r0
            virtual_ref %r0 -> %r1
            ref_return %r1
        """, transform=True, cc=FakeCallControlWithVRefInfo())

    def test_vref_forced(self):
        class X:
            pass
        def f():
            vref = jit.virtual_ref(X())
            return vref()
        # The call vref() is a jit_force_virtual operation in the original
        # graph.  It becomes in the JitCode a residual call to a helper that
        # contains itself a jit_force_virtual.
        self.encoding_test(f, [], """
            new_with_vtable <Descr> -> %r0
            virtual_ref %r0 -> %r1
            -live-
            residual_call_r_r $<* fn jit_force_virtual>, R[%r1], <Descr> -> %r2
            ref_return %r2
        """, transform=True, cc=FakeCallControlWithVRefInfo())

    def test_array_operations(self):
        A = lltype.GcArray(lltype.Signed)
        def f():
            array = lltype.malloc(A, 5)
            array[2] = 5
            return array[2] + len(array)
        self.encoding_test(f, [], """
            new_array $5, <Descr> -> %r0
            setarrayitem_gc_i %r0, $2, $5, <Descr>
            getarrayitem_gc_i %r0, $2, <Descr> -> %i0
            arraylen_gc %r0, <Descr> -> %i1
            int_add %i0, %i1 -> %i2
            int_return %i2
        """, transform=True)

    def test_void_array_operations(self):
        A = lltype.GcArray(lltype.Void)
        def f():
            array = lltype.malloc(A, 5)
            array[2] = None
            x = array[2]
            return len(array)
        self.encoding_test(f, [], """
            new_array $5, <Descr> -> %r0
            arraylen_gc %r0, <Descr> -> %i0
            int_return %i0
        """, transform=True)

    def test_string_operations(self):
        from rpython.rtyper.lltypesystem import rstr
        def f(n):
            s = lltype.malloc(rstr.STR, 2)
            s.chars[1] = chr(n)
            return ord(s.chars[1]) + len(s.chars)
        self.encoding_test(f, [512], """
            newstr $2 -> %r0
            strsetitem %r0, $1, %i0
            strgetitem %r0, $1 -> %i1
            strlen %r0 -> %i2
            int_add %i1, %i2 -> %i3
            int_return %i3
        """, transform=True)

    def test_uint_operations(self):
        def f(n):
            return ((r_uint(n) - 123) >> 1) <= r_uint(456)
        self.encoding_test(f, [200], """
            int_sub %i0, $123L -> %i1
            uint_rshift %i1, $1 -> %i2
            uint_le %i2, $456L -> %i3
            int_return %i3
        """, transform=True)

    def test_int_between(self):
        from rpython.rtyper.lltypesystem.lloperation import llop
        def f(n, m, p):
            return llop.int_between(lltype.Bool, n, m, p)
        self.encoding_test(f, [5, 6, 7], """
            int_between %i0, %i1, %i2 -> %i3
            int_return %i3
        """, transform=True)

    def test_force_cast(self):
        # NB: we don't need to test for INT here, the logic in jtransform is
        # general enough so that if we have the below cases it should
        # generalize also to INT
        for FROM, TO, expected in [
            (rffi.SIGNEDCHAR, rffi.SIGNEDCHAR, ""),
            (rffi.SIGNEDCHAR, rffi.UCHAR, "int_and %i0, $255 -> %i1"),
            (rffi.SIGNEDCHAR, rffi.SHORT, ""),
            (rffi.SIGNEDCHAR, rffi.USHORT, "int_and %i0, $65535 -> %i1"),
            (rffi.SIGNEDCHAR, rffi.LONG, ""),
            (rffi.SIGNEDCHAR, rffi.ULONG, ""),

            (rffi.UCHAR, rffi.SIGNEDCHAR, "int_signext %i0, $1 -> %i1"),
            (rffi.UCHAR, rffi.UCHAR, ""),
            (rffi.UCHAR, rffi.SHORT, ""),
            (rffi.UCHAR, rffi.USHORT, ""),
            (rffi.UCHAR, rffi.LONG, ""),
            (rffi.UCHAR, rffi.ULONG, ""),

            (rffi.SHORT, rffi.SIGNEDCHAR, "int_signext %i0, $1 -> %i1"),
            (rffi.SHORT, rffi.UCHAR, "int_and %i0, $255 -> %i1"),
            (rffi.SHORT, rffi.SHORT, ""),
            (rffi.SHORT, rffi.USHORT, "int_and %i0, $65535 -> %i1"),
            (rffi.SHORT, rffi.LONG, ""),
            (rffi.SHORT, rffi.ULONG, ""),

            (rffi.USHORT, rffi.SIGNEDCHAR, "int_signext %i0, $1 -> %i1"),
            (rffi.USHORT, rffi.UCHAR, "int_and %i0, $255 -> %i1"),
            (rffi.USHORT, rffi.SHORT, "int_signext %i0, $2 -> %i1"),
            (rffi.USHORT, rffi.USHORT, ""),
            (rffi.USHORT, rffi.LONG, ""),
            (rffi.USHORT, rffi.ULONG, ""),
            (rffi.USHORT, lltype.Bool, "int_is_true %i0 -> %i1"),

            (rffi.LONG, rffi.SIGNEDCHAR, "int_signext %i0, $1 -> %i1"),
            (rffi.LONG, rffi.UCHAR, "int_and %i0, $255 -> %i1"),
            (rffi.LONG, rffi.SHORT, "int_signext %i0, $2 -> %i1"),
            (rffi.LONG, rffi.USHORT, "int_and %i0, $65535 -> %i1"),
            (rffi.LONG, lltype.Bool, "int_is_true %i0 -> %i1"),
            (rffi.LONG, rffi.LONG, ""),
            (rffi.LONG, rffi.ULONG, ""),

            (rffi.ULONG, rffi.SIGNEDCHAR, "int_signext %i0, $1 -> %i1"),
            (rffi.ULONG, rffi.UCHAR, "int_and %i0, $255 -> %i1"),
            (rffi.ULONG, rffi.SHORT, "int_signext %i0, $2 -> %i1"),
            (rffi.ULONG, rffi.USHORT, "int_and %i0, $65535 -> %i1"),
            (rffi.ULONG, lltype.Bool, "int_is_true %i0 -> %i1"),
            (rffi.ULONG, rffi.LONG, ""),
            (rffi.ULONG, rffi.ULONG, ""),
            ]:
            expected = [s.strip() for s in expected.splitlines()]
            check_force_cast(FROM, TO, expected, 42)
            check_force_cast(FROM, TO, expected, -42)
            returnvar = "%i" + str(len(expected))
            expected.append('int_return ' + returnvar)
            expectedstr = '\n'.join(expected)
            #
            def f(n):
                return rffi.cast(TO, n)
            self.encoding_test(f, [rffi.cast(FROM, 42)], expectedstr,
                               transform=True)

            if not longlong.is_64_bit:
                if FROM in (rffi.LONG, rffi.ULONG):
                    if FROM == rffi.LONG:
                        FROM = rffi.LONGLONG
                    else:
                        FROM = rffi.ULONGLONG
                    if TO == lltype.Bool:
                        prefix = 'u' if FROM == rffi.ULONGLONG else ''
                        expected = [
                            "residual_call_irf_i $<* fn %sllong_ne>, I[], R[], F[%%f0, $0L], <Descr> -> %%i0" % prefix,
                            "int_return %i0",
                        ]
                    else:
                        expected.insert(0,
                            "residual_call_irf_i $<* fn llong_to_int>, I[], R[], F[%f0], <Descr> -> %i0")
                    expectedstr = '\n'.join(expected)
                    self.encoding_test(f, [rffi.cast(FROM, 42)], expectedstr,
                                       transform=True)
                elif TO in (rffi.LONG, rffi.ULONG):
                    if rffi.cast(FROM, -1) < 0:
                        fnname = "llong_from_int"
                    else:
                        fnname = "llong_from_uint"
                    if TO == rffi.LONG:
                        TO = rffi.LONGLONG
                    else:
                        TO = rffi.ULONGLONG
                        fnname = "u" + fnname
                    expected.pop()   # remove int_return
                    expected.append(
                        "residual_call_irf_f $<* fn %s>, I[%s], R[], F[], <Descr> -> %%f0"
                        % (fnname, returnvar))
                    expected.append("float_return %f0")
                    expectedstr = '\n'.join(expected)
                    self.encoding_test(f, [rffi.cast(FROM, 42)], expectedstr,
                                       transform=True)

    def test_force_cast_pointer(self):
        def h(p):
            return rffi.cast(rffi.VOIDP, p)
        self.encoding_test(h, [lltype.nullptr(rffi.CCHARP.TO)], """
            int_return %i0
        """, transform=True)

    def test_force_cast_floats(self):
        # Caststs to lltype.Float
        def f(n):
            return rffi.cast(lltype.Float, n)
        self.encoding_test(f, [12.456], """
            float_return %f0
        """, transform=True)
        self.encoding_test(f, [rffi.cast(rffi.SIGNEDCHAR, 42)], """
            cast_int_to_float %i0 -> %f0
            float_return %f0
        """, transform=True)
        def f(n):
            return rffi.cast(lltype.Bool, n)
        self.encoding_test(f, [0.1], """
            float_ne %f0, $0.0 -> %i0
            int_return %i0
        """, transform=True)
        self.encoding_test(f, [rffi.cast(lltype.SingleFloat, 0.5)], """
            cast_singlefloat_to_float %i0 -> %f0
            float_ne %f0, $0.0 -> %i1
            int_return %i1
        """, transform=True)

        # Casts to lltype.SingleFloat
        def g(n):
            return rffi.cast(lltype.SingleFloat, n)
        self.encoding_test(g, [12.456], """
            cast_float_to_singlefloat %f0 -> %i0
            int_return %i0
        """, transform=True)
        self.encoding_test(g, [rffi.cast(rffi.SIGNEDCHAR, 42)], """
            cast_int_to_float %i0 -> %f0
            cast_float_to_singlefloat %f0 -> %i1
            int_return %i1
        """, transform=True)

        # Casts from floats
        def f(n):
            return rffi.cast(rffi.SIGNEDCHAR, n)
        self.encoding_test(f, [12.456], """
            cast_float_to_int %f0 -> %i0
            int_signext %i0, $1 -> %i1
            int_return %i1
        """, transform=True)
        self.encoding_test(f, [rffi.cast(lltype.SingleFloat, 12.456)], """
            cast_singlefloat_to_float %i0 -> %f0
            cast_float_to_int %f0 -> %i1
            int_signext %i1, $1 -> %i2
            int_return %i2
        """, transform=True)

        def f(dbl):
            return rffi.cast(rffi.UCHAR, dbl)
        self.encoding_test(f, [12.456], """
            cast_float_to_int %f0 -> %i0
            int_and %i0, $255 -> %i1
            int_return %i1
        """, transform=True)

        def f(dbl):
            return rffi.cast(lltype.Unsigned, dbl)
        self.encoding_test(f, [12.456], """
            residual_call_irf_i $<* fn cast_float_to_uint>, I[], R[], F[%f0], <Descr> -> %i0
            int_return %i0
        """, transform=True)

        def f(i):
            return rffi.cast(lltype.Float, chr(i))    # "char -> float"
        self.encoding_test(f, [12], """
            cast_int_to_float %i0 -> %f0
            float_return %f0
        """, transform=True)

        def f(i):
            return rffi.cast(lltype.Float, r_uint(i))    # "uint -> float"
        self.encoding_test(f, [12], """
            residual_call_irf_f $<* fn cast_uint_to_float>, I[%i0], R[], F[], <Descr> -> %f0
            float_return %f0
        """, transform=True)

        if not longlong.is_64_bit:
            def f(dbl):
                return rffi.cast(lltype.SignedLongLong, dbl)
            self.encoding_test(f, [12.3], """
                residual_call_irf_f $<* fn llong_from_float>, I[], R[], F[%f0], <Descr> -> %f1
                float_return %f1
            """, transform=True)

            def f(dbl):
                return rffi.cast(lltype.UnsignedLongLong, dbl)
            self.encoding_test(f, [12.3], """
                residual_call_irf_f $<* fn ullong_from_float>, I[], R[], F[%f0], <Descr> -> %f1
                float_return %f1
            """, transform=True)

            def f(x):
                ll = r_longlong(x)
                return rffi.cast(lltype.Float, ll)
            self.encoding_test(f, [12], """
                residual_call_irf_f $<* fn llong_from_int>, I[%i0], R[], F[], <Descr> -> %f0
                residual_call_irf_f $<* fn llong_to_float>, I[], R[], F[%f0], <Descr> -> %f1
                float_return %f1
            """, transform=True)

            def f(x):
                ll = r_ulonglong(x)
                return rffi.cast(lltype.Float, ll)
            self.encoding_test(f, [12], """
                residual_call_irf_f $<* fn ullong_from_int>, I[%i0], R[], F[], <Descr> -> %f0
                residual_call_irf_f $<* fn ullong_u_to_float>, I[], R[], F[%f0], <Descr> -> %f1
                float_return %f1
            """, transform=True)

    def test_direct_ptradd(self):
        def f(p, n):
            return lltype.direct_ptradd(p, n)
        self.encoding_test(f, [lltype.nullptr(rffi.CCHARP.TO), 123], """
            int_add %i0, %i1 -> %i2
            int_return %i2
        """, transform=True)

    def test_direct_ptradd_2(self):
        def f(p, n):
            return lltype.direct_ptradd(p, n + 2)
        self.encoding_test(f, [lltype.nullptr(rffi.SHORTP.TO), 123], """
            int_add %i1, $2 -> %i2
            int_mul %i2, $<ItemOffset <SHORT> 1> -> %i3
            int_add %i0, %i3 -> %i4
            int_return %i4
        """, transform=True)

    def test_convert_float_bytes(self):
        from rpython.rlib.longlong2float import float2longlong, longlong2float
        def f(x):
            ll = float2longlong(x)
            return longlong2float(ll)
        if longlong.is_64_bit:
            tmp_var = "%i0"
            result_var = "%f1"
        else:
            tmp_var = "%f1"
            result_var = "%f2"
        self.encoding_test(f, [25.0], """
            convert_float_bytes_to_longlong %%f0 -> %(tmp_var)s
            convert_longlong_bytes_to_float %(tmp_var)s -> %(result_var)s
            float_return %(result_var)s
        """ % {"result_var": result_var, "tmp_var": tmp_var}, transform=True)

    def test_vable_attribute_list_is_not_None(self):
        class F:
            _virtualizable_ = ['vlist[*]']
            vlist = None
            def __init__(self, x):
                self.vlist = [x]
        def g():
            return F(42)
        def f():
            f = g()
            if f.vlist is not None:
                pass
        e = py.test.raises(AssertionError, self.encoding_test, f, [], "!",
                           transform=True)
        assert str(e.value).startswith("A virtualizable array is passed aroun")
        assert "<Descr>" in str(e.value)

    def test_vable_attribute_list_copied_around(self):
        class F:
            _virtualizable_ = ['vlist[*]']
            vlist = None
            def __init__(self, x):
                self.vlist = [x]
        def g():
            return F(42)
        def f():
            f = g()
            f.extrastuff = f.vlist
        e = py.test.raises(AssertionError, self.encoding_test, f, [], "!",
                           transform=True)
        assert str(e.value).startswith("A virtualizable array is passed aroun")
        assert "<Descr>" in str(e.value)


def check_force_cast(FROM, TO, operations, value):
    """Check that the test is correctly written..."""
    import re
    r = re.compile('(\w+) \%i\d, \$(-?\d+)')
    r2 = re.compile('(\w+) \%i\d')
    #
    value = rffi.cast(FROM, value)
    value = rffi.cast(lltype.Signed, value)
    #
    expected_value = rffi.cast(TO, value)
    expected_value = rffi.cast(lltype.Signed, expected_value)
    #
    for op in operations:
        match = r.match(op)
        if match is None:
            match = r2.match(op)
        assert match, "line %r does not match regexp" % (op,)
        opname = match.group(1)
        if opname == 'int_and':
            value &= int(match.group(2))
        elif opname == 'int_signext':
            numbytes = int(match.group(2))
            value = int_signext(value, numbytes)
        elif opname == 'int_is_true':
            value = bool(value)
        else:
            assert 0, opname
    #
    assert rffi.cast(lltype.Signed, value) == expected_value
