"""
Minimal-API wrapper around the llinterpreter to run operations.
"""

from pypy.rlib.unroll import unrolling_iterable
from pypy.rlib.objectmodel import we_are_translated
from pypy.rpython.lltypesystem import lltype, llmemory, rclass
from pypy.rpython.ootypesystem import ootype
from pypy.rpython.llinterp import LLInterpreter
from pypy.jit.metainterp import history
from pypy.jit.metainterp.history import REF, INT, FLOAT, STRUCT
from pypy.jit.metainterp.warmstate import unwrap
from pypy.jit.metainterp.resoperation import rop
from pypy.jit.backend import model
from pypy.jit.backend.llgraph import llimpl, symbolic
from pypy.jit.metainterp.typesystem import llhelper, oohelper
from pypy.jit.codewriter import heaptracker, longlong

class MiniStats:
    pass


class Descr(history.AbstractDescr):

    def __init__(self, ofs, typeinfo, extrainfo=None, name=None,
                 arg_types=None, count_fields_if_immut=-1, ffi_flags=0, width=-1):

        self.ofs = ofs
        self.width = width
        self.typeinfo = typeinfo
        self.extrainfo = extrainfo
        self.name = name
        self.arg_types = arg_types
        self.count_fields_if_immut = count_fields_if_immut
        self.ffi_flags = ffi_flags

    def get_arg_types(self):
        return self.arg_types

    def get_result_type(self):
        return self.typeinfo

    def get_extra_info(self):
        return self.extrainfo

    def sort_key(self):
        """Returns an integer that can be used as a key when sorting the
        field descrs of a single structure.  The property that this
        number has is simply that two different field descrs of the same
        structure give different numbers."""
        return self.ofs

    def is_pointer_field(self):
        return self.typeinfo == REF

    def is_float_field(self):
        return self.typeinfo == FLOAT

    def is_array_of_pointers(self):
        return self.typeinfo == REF

    def is_array_of_floats(self):
        return self.typeinfo == FLOAT

    def is_array_of_structs(self):
        return self.typeinfo == STRUCT

    def as_vtable_size_descr(self):
        return self

    def count_fields_if_immutable(self):
        return self.count_fields_if_immut

    def get_ffi_flags(self):
        return self.ffi_flags

    def __lt__(self, other):
        raise TypeError("cannot use comparison on Descrs")
    def __le__(self, other):
        raise TypeError("cannot use comparison on Descrs")
    def __gt__(self, other):
        raise TypeError("cannot use comparison on Descrs")
    def __ge__(self, other):
        raise TypeError("cannot use comparison on Descrs")

    def __repr__(self):
        args = [repr(self.ofs), repr(self.typeinfo)]
        if self.name is not None:
            args.append(repr(self.name))
        if self.extrainfo is not None:
            args.append('E')
        return '<Descr %r>' % (', '.join(args),)


history.TreeLoop._compiled_version = lltype.nullptr(llimpl.COMPILEDLOOP.TO)


class BaseCPU(model.AbstractCPU):
    supports_floats = True
    supports_longlong = llimpl.IS_32_BIT
    supports_singlefloats = True

    def __init__(self, rtyper, stats=None, opts=None,
                 translate_support_code=False,
                 annmixlevel=None, gcdescr=None):
        assert type(opts) is not bool
        model.AbstractCPU.__init__(self)
        self.rtyper = rtyper
        self.translate_support_code = translate_support_code
        self.stats = stats or MiniStats()
        self.stats.exec_counters = {}
        self.stats.exec_jumps = 0
        self.stats.exec_conditional_jumps = 0
        llimpl._stats = self.stats
        llimpl._llinterp = LLInterpreter(self.rtyper)
        self._future_values = []
        self._descrs = {}

    def _freeze_(self):
        assert self.translate_support_code
        return False

    def getdescr(self, ofs, typeinfo='?', extrainfo=None, name=None,
                 arg_types=None, count_fields_if_immut=-1, ffi_flags=0, width=-1):
        key = (ofs, typeinfo, extrainfo, name, arg_types,
               count_fields_if_immut, ffi_flags, width)
        try:
            return self._descrs[key]
        except KeyError:
            descr = Descr(ofs, typeinfo, extrainfo, name, arg_types,
                          count_fields_if_immut, ffi_flags, width)
            self._descrs[key] = descr
            return descr

    def compile_bridge(self, faildescr, inputargs, operations,
                       original_loop_token, log=True):
        c = llimpl.compile_start()
        clt = original_loop_token.compiled_loop_token
        clt.loop_and_bridges.append(c)
        clt.compiling_a_bridge()
        self._compile_loop_or_bridge(c, inputargs, operations, clt)
        old, oldindex = faildescr._compiled_fail
        llimpl.compile_redirect_fail(old, oldindex, c)

    def compile_loop(self, inputargs, operations, jitcell_token,
                     log=True, name=''):
        """In a real assembler backend, this should assemble the given
        list of operations.  Here we just generate a similar CompiledLoop
        instance.  The code here is RPython, whereas the code in llimpl
        is not.
        """
        c = llimpl.compile_start()
        clt = model.CompiledLoopToken(self, jitcell_token.number)
        clt.loop_and_bridges = [c]
        clt.compiled_version = c
        jitcell_token.compiled_loop_token = clt
        self._compile_loop_or_bridge(c, inputargs, operations, clt)

    def free_loop_and_bridges(self, compiled_loop_token):
        for c in compiled_loop_token.loop_and_bridges:
            llimpl.mark_as_free(c)
        model.AbstractCPU.free_loop_and_bridges(self, compiled_loop_token)

    def _compile_loop_or_bridge(self, c, inputargs, operations, clt):
        var2index = {}
        for box in inputargs:
            if isinstance(box, history.BoxInt):
                var2index[box] = llimpl.compile_start_int_var(c)
            elif isinstance(box, self.ts.BoxRef):
                TYPE = self.ts.BASETYPE
                var2index[box] = llimpl.compile_start_ref_var(c, TYPE)
            elif isinstance(box, history.BoxFloat):
                var2index[box] = llimpl.compile_start_float_var(c)
            else:
                raise Exception("box is: %r" % (box,))
        llimpl.compile_started_vars(clt)
        self._compile_operations(c, operations, var2index, clt)
        return c

    def _compile_operations(self, c, operations, var2index, clt):
        for op in operations:
            llimpl.compile_add(c, op.getopnum())
            descr = op.getdescr()
            if isinstance(descr, Descr):
                llimpl.compile_add_descr(c, descr.ofs, descr.typeinfo,
                                         descr.arg_types, descr.extrainfo,
                                         descr.width)
            if isinstance(descr, history.JitCellToken):
                assert op.getopnum() != rop.JUMP
                llimpl.compile_add_loop_token(c, descr)
            if isinstance(descr, history.TargetToken) and op.getopnum() == rop.LABEL:
                llimpl.compile_add_target_token(c, descr, clt)
            if self.is_oo and isinstance(descr, (OODescr, MethDescr)):
                # hack hack, not rpython
                c._obj.externalobj.operations[-1].setdescr(descr)
            for i in range(op.numargs()):
                x = op.getarg(i)
                if isinstance(x, history.Box):
                    llimpl.compile_add_var(c, var2index[x])
                elif isinstance(x, history.ConstInt):
                    llimpl.compile_add_int_const(c, x.value)
                elif isinstance(x, self.ts.ConstRef):
                    llimpl.compile_add_ref_const(c, x.value, self.ts.BASETYPE)
                elif isinstance(x, history.ConstFloat):
                    llimpl.compile_add_float_const(c, x.value)
                elif isinstance(x, Descr):
                    llimpl.compile_add_descr_arg(c, x.ofs, x.typeinfo,
                                                 x.arg_types)
                else:
                    raise Exception("'%s' args contain: %r" % (op.getopname(),
                                                               x))
            if op.is_guard():
                faildescr = op.getdescr()
                assert isinstance(faildescr, history.AbstractFailDescr)
                faildescr._fail_args_types = []
                for box in op.getfailargs():
                    if box is None:
                        type = history.HOLE
                    else:
                        type = box.type
                    faildescr._fail_args_types.append(type)
                fail_index = self.get_fail_descr_number(faildescr)
                index = llimpl.compile_add_fail(c, fail_index)
                faildescr._compiled_fail = c, index
                for box in op.getfailargs():
                    if box is not None:
                        llimpl.compile_add_fail_arg(c, var2index[box])
                    else:
                        llimpl.compile_add_fail_arg(c, -1)

            x = op.result
            if x is not None:
                if isinstance(x, history.BoxInt):
                    var2index[x] = llimpl.compile_add_int_result(c)
                elif isinstance(x, self.ts.BoxRef):
                    var2index[x] = llimpl.compile_add_ref_result(c, self.ts.BASETYPE)
                elif isinstance(x, history.BoxFloat):
                    var2index[x] = llimpl.compile_add_float_result(c)
                else:
                    raise Exception("%s.result contain: %r" % (op.getopname(),
                                                               x))
        op = operations[-1]
        assert op.is_final()
        if op.getopnum() == rop.JUMP:
            targettoken = op.getdescr()
            llimpl.compile_add_jump_target(c, targettoken, clt)
        elif op.getopnum() == rop.FINISH:
            faildescr = op.getdescr()
            index = self.get_fail_descr_number(faildescr)
            llimpl.compile_add_fail(c, index)
        else:
            assert False, "unknown operation"

    def _execute_token(self, loop_token):
        compiled_version = loop_token.compiled_loop_token.compiled_version
        frame = llimpl.new_frame(self.is_oo, self)
        # setup the frame
        llimpl.frame_clear(frame, compiled_version)
        # run the loop
        fail_index = llimpl.frame_execute(frame)
        # we hit a FAIL operation.
        self.latest_frame = frame
        return fail_index

    def make_execute_token(self, *argtypes):
        nb_args = len(argtypes)
        unroll_argtypes = unrolling_iterable(list(enumerate(argtypes)))
        #
        def execute_token(loop_token, *args):
            assert len(args) == nb_args
            for index, TYPE in unroll_argtypes:
                x = args[index]
                assert TYPE == lltype.typeOf(x)
                if TYPE == lltype.Signed:
                    llimpl.set_future_value_int(index, x)
                elif TYPE == llmemory.GCREF:
                    llimpl.set_future_value_ref(index, x)
                elif TYPE == longlong.FLOATSTORAGE:
                    llimpl.set_future_value_float(index, x)
                else:
                    assert 0
            #
            fail_index = self._execute_token(loop_token)
            return self.get_fail_descr_from_number(fail_index)
        #
        return execute_token

    def get_latest_value_int(self, index):
        return llimpl.frame_int_getvalue(self.latest_frame, index)

    def get_latest_value_ref(self, index):
        return llimpl.frame_ptr_getvalue(self.latest_frame, index)

    def get_latest_value_float(self, index):
        return llimpl.frame_float_getvalue(self.latest_frame, index)

    def get_latest_value_count(self):
        return llimpl.frame_get_value_count(self.latest_frame)

    def get_latest_force_token(self):
        token = llimpl.get_frame_forced_token(self.latest_frame)
        return heaptracker.adr2int(token)

    def clear_latest_values(self, count):
        llimpl.frame_clear_latest_values(self.latest_frame, count)

    def redirect_call_assembler(self, oldlooptoken, newlooptoken):
        if we_are_translated():
            raise ValueError("CALL_ASSEMBLER not supported")
        llimpl.redirect_call_assembler(self, oldlooptoken, newlooptoken)

    def invalidate_loop(self, looptoken):
        for loop in looptoken.compiled_loop_token.loop_and_bridges:
            loop._obj.externalobj.invalid = True

    # ----------

    def sizeof(self, S):
        assert not isinstance(S, lltype.Ptr)
        count = heaptracker.count_fields_if_immutable(S)
        return self.getdescr(symbolic.get_size(S), count_fields_if_immut=count)


class LLtypeCPU(BaseCPU):
    is_oo = False
    ts = llhelper

    def __init__(self, *args, **kwds):
        BaseCPU.__init__(self, *args, **kwds)
        self.fielddescrof_vtable = self.fielddescrof(rclass.OBJECT, 'typeptr')

    def fielddescrof(self, S, fieldname):
        ofs, size = symbolic.get_field_token(S, fieldname)
        token = history.getkind(getattr(S, fieldname))
        return self.getdescr(ofs, token[0], name=fieldname)

    def fielddescrof_dynamic(self, offset, fieldsize, is_pointer, is_float, is_signed):
        if is_pointer:
            typeinfo = REF
        elif is_float:
            typeinfo = FLOAT
        else:
            typeinfo = INT
        # we abuse the arg_types field to distinguish dynamic and static descrs
        return self.getdescr(offset, typeinfo, arg_types='dynamic', name='<dynamic field>')

    def interiorfielddescrof(self, A, fieldname):
        S = A.OF
        width = symbolic.get_size(A)
        ofs, size = symbolic.get_field_token(S, fieldname)
        token = history.getkind(getattr(S, fieldname))
        return self.getdescr(ofs, token[0], name=fieldname, width=width)

    def interiorfielddescrof_dynamic(self, offset, width, fieldsize,
        is_pointer, is_float, is_signed):

        if is_pointer:
            typeinfo = REF
        elif is_float:
            typeinfo = FLOAT
        else:
            typeinfo = INT
        # we abuse the arg_types field to distinguish dynamic and static descrs
        return Descr(offset, typeinfo, arg_types='dynamic', name='<dynamic interior field>', width=width)

    def calldescrof(self, FUNC, ARGS, RESULT, extrainfo):
        arg_types = []
        for ARG in ARGS:
            token = history.getkind(ARG)
            if token != 'void':
                if token == 'float' and longlong.is_longlong(ARG):
                    token = 'L'
                arg_types.append(token[0])
        token = history.getkind(RESULT)
        if token == 'float' and longlong.is_longlong(RESULT):
            token = 'L'
        return self.getdescr(0, token[0], extrainfo=extrainfo,
                             arg_types=''.join(arg_types))

    def calldescrof_dynamic(self, ffi_args, ffi_result, extrainfo, ffi_flags):
        from pypy.jit.backend.llsupport.ffisupport import get_ffi_type_kind
        from pypy.jit.backend.llsupport.ffisupport import UnsupportedKind
        arg_types = []
        try:
            for arg in ffi_args:
                kind = get_ffi_type_kind(self, arg)
                if kind != history.VOID:
                    arg_types.append(kind)
            reskind = get_ffi_type_kind(self, ffi_result)
        except UnsupportedKind:
            return None
        return self.getdescr(0, reskind, extrainfo=extrainfo,
                             arg_types=''.join(arg_types),
                             ffi_flags=ffi_flags)


    def grab_exc_value(self):
        return llimpl.grab_exc_value()

    def arraydescrof(self, A):
        assert A.OF != lltype.Void
        assert isinstance(A, lltype.GcArray) or A._hints.get('nolength', False)
        size = symbolic.get_size(A)
        if isinstance(A.OF, lltype.Ptr) or isinstance(A.OF, lltype.Primitive):
            token = history.getkind(A.OF)[0]
        elif isinstance(A.OF, lltype.Struct):
            token = 's'
        else:
            token = '?'
        return self.getdescr(size, token)

    # ---------- the backend-dependent operations ----------

    def bh_strlen(self, string):
        return llimpl.do_strlen(string)

    def bh_strgetitem(self, string, index):
        return llimpl.do_strgetitem(string, index)

    def bh_unicodelen(self, string):
        return llimpl.do_unicodelen(string)

    def bh_unicodegetitem(self, string, index):
        return llimpl.do_unicodegetitem(string, index)

    def bh_getarrayitem_gc_i(self, arraydescr, array, index):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_getarrayitem_gc_int(array, index)
    def bh_getarrayitem_raw_i(self, arraydescr, array, index):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_getarrayitem_raw_int(array, index)
    def bh_getarrayitem_gc_r(self, arraydescr, array, index):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_getarrayitem_gc_ptr(array, index)
    def bh_getarrayitem_gc_f(self, arraydescr, array, index):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_getarrayitem_gc_float(array, index)
    def bh_getarrayitem_raw_f(self, arraydescr, array, index):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_getarrayitem_raw_float(array, index)

    def bh_getfield_gc_i(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_gc_int(struct, fielddescr.ofs)
    def bh_getfield_gc_r(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_gc_ptr(struct, fielddescr.ofs)
    def bh_getfield_gc_f(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_gc_float(struct, fielddescr.ofs)

    def bh_getfield_raw_i(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_raw_int(struct, fielddescr.ofs)
    def bh_getfield_raw_r(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_raw_ptr(struct, fielddescr.ofs)
    def bh_getfield_raw_f(self, struct, fielddescr):
        assert isinstance(fielddescr, Descr)
        return llimpl.do_getfield_raw_float(struct, fielddescr.ofs)

    def bh_getinteriorfield_gc_i(self, array, index, descr):
        assert isinstance(descr, Descr)
        return llimpl.do_getinteriorfield_gc_int(array, index, descr.ofs)
    def bh_getinteriorfield_gc_r(self, array, index, descr):
        assert isinstance(descr, Descr)
        return llimpl.do_getinteriorfield_gc_ptr(array, index, descr.ofs)
    def bh_getinteriorfield_gc_f(self, array, index, descr):
        assert isinstance(descr, Descr)
        return llimpl.do_getinteriorfield_gc_float(array, index, descr.ofs)

    def bh_setinteriorfield_gc_i(self, array, index, descr, value):
        assert isinstance(descr, Descr)
        return llimpl.do_setinteriorfield_gc_int(array, index, descr.ofs,
                                                 value)
    def bh_setinteriorfield_gc_r(self, array, index, descr, value):
        assert isinstance(descr, Descr)
        return llimpl.do_setinteriorfield_gc_ptr(array, index, descr.ofs,
                                                 value)
    def bh_setinteriorfield_gc_f(self, array, index, descr, value):
        assert isinstance(descr, Descr)
        return llimpl.do_setinteriorfield_gc_float(array, index, descr.ofs,
                                                   value)

    def bh_new(self, sizedescr):
        assert isinstance(sizedescr, Descr)
        return llimpl.do_new(sizedescr.ofs)

    def bh_new_with_vtable(self, sizedescr, vtable):
        assert isinstance(sizedescr, Descr)
        result = llimpl.do_new(sizedescr.ofs)
        llimpl.do_setfield_gc_int(result, self.fielddescrof_vtable.ofs, vtable)
        return result

    def bh_classof(self, struct):
        struct = lltype.cast_opaque_ptr(rclass.OBJECTPTR, struct)
        result_adr = llmemory.cast_ptr_to_adr(struct.typeptr)
        return heaptracker.adr2int(result_adr)

    def bh_new_array(self, arraydescr, length):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_new_array(arraydescr.ofs, length)

    def bh_arraylen_gc(self, arraydescr, array):
        assert isinstance(arraydescr, Descr)
        return llimpl.do_arraylen_gc(arraydescr, array)

    def bh_setarrayitem_gc_i(self, arraydescr, array, index, newvalue):
        assert isinstance(arraydescr, Descr)
        llimpl.do_setarrayitem_gc_int(array, index, newvalue)

    def bh_setarrayitem_raw_i(self, arraydescr, array, index, newvalue):
        assert isinstance(arraydescr, Descr)
        llimpl.do_setarrayitem_raw_int(array, index, newvalue)

    def bh_setarrayitem_gc_r(self, arraydescr, array, index, newvalue):
        assert isinstance(arraydescr, Descr)
        llimpl.do_setarrayitem_gc_ptr(array, index, newvalue)

    def bh_setarrayitem_gc_f(self, arraydescr, array, index, newvalue):
        assert isinstance(arraydescr, Descr)
        llimpl.do_setarrayitem_gc_float(array, index, newvalue)

    def bh_setarrayitem_raw_f(self, arraydescr, array, index, newvalue):
        assert isinstance(arraydescr, Descr)
        llimpl.do_setarrayitem_raw_float(array, index, newvalue)

    def bh_setfield_gc_i(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_gc_int(struct, fielddescr.ofs, newvalue)
    def bh_setfield_gc_r(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_gc_ptr(struct, fielddescr.ofs, newvalue)
    def bh_setfield_gc_f(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_gc_float(struct, fielddescr.ofs, newvalue)

    def bh_setfield_raw_i(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_raw_int(struct, fielddescr.ofs, newvalue)
    def bh_setfield_raw_r(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_raw_ptr(struct, fielddescr.ofs, newvalue)
    def bh_setfield_raw_f(self, struct, fielddescr, newvalue):
        assert isinstance(fielddescr, Descr)
        llimpl.do_setfield_raw_float(struct, fielddescr.ofs, newvalue)

    def bh_newstr(self, length):
        return llimpl.do_newstr(length)

    def bh_newunicode(self, length):
        return llimpl.do_newunicode(length)

    def bh_strsetitem(self, string, index, newvalue):
        llimpl.do_strsetitem(string, index, newvalue)

    def bh_unicodesetitem(self, string, index, newvalue):
        llimpl.do_unicodesetitem(string, index, newvalue)

    def bh_call_i(self, func, calldescr, args_i, args_r, args_f):
        self._prepare_call(INT, calldescr, args_i, args_r, args_f)
        return llimpl.do_call_int(func)
    def bh_call_r(self, func, calldescr, args_i, args_r, args_f):
        self._prepare_call(REF, calldescr, args_i, args_r, args_f)
        return llimpl.do_call_ptr(func)
    def bh_call_f(self, func, calldescr, args_i, args_r, args_f):
        self._prepare_call(FLOAT + 'L', calldescr, args_i, args_r, args_f)
        return llimpl.do_call_float(func)
    def bh_call_v(self, func, calldescr, args_i, args_r, args_f):
        self._prepare_call('v', calldescr, args_i, args_r, args_f)
        llimpl.do_call_void(func)

    def _prepare_call(self, resulttypeinfo, calldescr, args_i, args_r, args_f):
        assert isinstance(calldescr, Descr)
        assert calldescr.typeinfo in resulttypeinfo
        if args_i is not None:
            for x in args_i:
                llimpl.do_call_pushint(x)
        if args_r is not None:
            for x in args_r:
                llimpl.do_call_pushptr(x)
        if args_f is not None:
            for x in args_f:
                llimpl.do_call_pushfloat(x)

    def force(self, force_token):
        token = llmemory.cast_int_to_adr(force_token)
        frame = llimpl.get_forced_token_frame(token)
        fail_index = llimpl.force(frame)
        self.latest_frame = frame
        return self.get_fail_descr_from_number(fail_index)


class OOtypeCPU_xxx_disabled(BaseCPU):
    is_oo = True
    ts = oohelper

    @staticmethod
    def fielddescrof(T, fieldname):
        # use class where the field is really defined as a key
        T1, _ = T._lookup_field(fieldname)
        return FieldDescr.new(T1, fieldname)

    @staticmethod
    def calldescrof(FUNC, ARGS, RESULT, extrainfo):
        return StaticMethDescr.new(FUNC, ARGS, RESULT, extrainfo)

    @staticmethod
    def methdescrof(SELFTYPE, methname):
        return MethDescr.new(SELFTYPE, methname)

    @staticmethod
    def typedescrof(TYPE):
        return TypeDescr.new(TYPE)

    @staticmethod
    def arraydescrof(A):
        assert isinstance(A, ootype.Array)
        TYPE = A.ITEM
        return TypeDescr.new(TYPE)

    def typedescr2classbox(self, descr):
        assert isinstance(descr, TypeDescr)
        return history.ConstObj(ootype.cast_to_object(
                            ootype.runtimeClass(descr.TYPE)))

    def get_exception(self):
        if llimpl._last_exception:
            e = llimpl._last_exception.args[0]
            return ootype.cast_to_object(e)
        else:
            return ootype.NULL

    def get_exc_value(self):
        if llimpl._last_exception:
            earg = llimpl._last_exception.args[1]
            return ootype.cast_to_object(earg)
        else:
            return ootype.NULL

    def get_overflow_error(self):
        ll_err = llimpl._get_error(OverflowError)
        return (ootype.cast_to_object(ll_err.args[0]),
                ootype.cast_to_object(ll_err.args[1]))

    def get_zero_division_error(self):
        ll_err = llimpl._get_error(ZeroDivisionError)
        return (ootype.cast_to_object(ll_err.args[0]),
                ootype.cast_to_object(ll_err.args[1]))

    def do_new_with_vtable(self, clsbox):
        cls = clsbox.getref_base()
        typedescr = self.class_sizes[cls]
        return typedescr.create()

    def do_new_array(self, lengthbox, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.create_array(lengthbox)

    def do_new(self, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.create()

    def do_runtimenew(self, classbox):
        "NOT_RPYTHON"
        classobj = classbox.getref(ootype.Class)
        res = ootype.runtimenew(classobj)
        return history.BoxObj(ootype.cast_to_object(res))

    def do_instanceof(self, box1, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.instanceof(box1)

    def do_getfield_gc(self, box1, fielddescr):
        assert isinstance(fielddescr, FieldDescr)
        return fielddescr.getfield(box1)

    def do_setfield_gc(self, box1, box2, fielddescr):
        assert isinstance(fielddescr, FieldDescr)
        return fielddescr.setfield(box1, box2)

    def do_getarrayitem_gc(self, box1, box2, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.getarrayitem(box1, box2)

    def do_setarrayitem_gc(self, box1, box2, box3, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.setarrayitem(box1, box2, box3)

    def do_arraylen_gc(self, box1, typedescr):
        assert isinstance(typedescr, TypeDescr)
        return typedescr.getarraylength(box1)

    def do_call_XXX(self, args, descr):
        assert isinstance(descr, StaticMethDescr)
        funcbox = args[0]
        argboxes = args[1:]
        x = descr.callfunc(funcbox, argboxes)
        # XXX: return None if RESULT is Void
        return x

    def do_oosend(self, args, descr):
        assert isinstance(descr, MethDescr)
        selfbox = args[0]
        argboxes = args[1:]
        x = descr.callmeth(selfbox, argboxes)
        # XXX: return None if METH.RESULT is Void
        return x


def make_getargs(ARGS):
    argsiter = unrolling_iterable(ARGS)
    args_n = len([ARG for ARG in ARGS if ARG is not ootype.Void])
    def getargs(argboxes):
        funcargs = ()
        assert len(argboxes) == args_n
        i = 0
        for ARG in argsiter:
            if ARG is ootype.Void:
                funcargs += (None,)
            else:
                box = argboxes[i]
                i+=1
                funcargs += (unwrap(ARG, box),)
        return funcargs
    return getargs

def boxresult(RESULT, result):
    if isinstance(RESULT, ootype.OOType):
        return history.BoxObj(ootype.cast_to_object(result))
    elif RESULT is lltype.Float:
        return history.BoxFloat(result)
    else:
        return history.BoxInt(lltype.cast_primitive(ootype.Signed, result))
boxresult._annspecialcase_ = 'specialize:arg(0)'


class KeyManager(object):
    """
    Helper class to convert arbitrary dictionary keys to integers.
    """

    def __init__(self):
        self.keys = {}

    def getkey(self, key):
        try:
            return self.keys[key]
        except KeyError:
            n = len(self.keys)
            self.keys[key] = n
            return n

    def _freeze_(self):
        raise Exception("KeyManager is not supposed to be turned into a pbc")


descr_cache = {}
class OODescr(history.AbstractDescr):

    @classmethod
    def new(cls, *args):
        'NOT_RPYTHON'
        key = (cls, args)
        try:
            return descr_cache[key]
        except KeyError:
            res = cls(*args)
            descr_cache[key] = res
            return res

class StaticMethDescr(OODescr):

    def __init__(self, FUNC, ARGS, RESULT, extrainfo=None):
        self.FUNC = FUNC
        getargs = make_getargs(FUNC.ARGS)
        def callfunc(funcbox, argboxes):
            funcobj = funcbox.getref(FUNC)
            funcargs = getargs(argboxes)
            res = llimpl.call_maybe_on_top_of_llinterp(funcobj, funcargs)
            if RESULT is not ootype.Void:
                return boxresult(RESULT, res)
        self.callfunc = callfunc
        self.extrainfo = extrainfo

    def get_extra_info(self):
        return self.extrainfo

class MethDescr(history.AbstractMethDescr):

    callmeth = None

    new = classmethod(OODescr.new.im_func)

    def __init__(self, SELFTYPE, methname):
        _, meth = SELFTYPE._lookup(methname)
        METH = ootype.typeOf(meth)
        self.SELFTYPE = SELFTYPE
        self.METH = METH
        self.methname = methname
        RESULT = METH.RESULT
        getargs = make_getargs(METH.ARGS)
        def callmeth(selfbox, argboxes):
            selfobj = selfbox.getref(SELFTYPE)
            meth = getattr(selfobj, methname)
            methargs = getargs(argboxes)
            res = llimpl.call_maybe_on_top_of_llinterp(meth, methargs)
            if RESULT is not ootype.Void:
                return boxresult(RESULT, res)
        self.callmeth = callmeth

    def __repr__(self):
        return '<MethDescr %r>' % self.methname

class TypeDescr(OODescr):

    create = None

    def __init__(self, TYPE):
        self.TYPE = TYPE
        self.ARRAY = ARRAY = ootype.Array(TYPE)
        def create():
            return boxresult(TYPE, ootype.new(TYPE))

        def create_array(lengthbox):
            n = lengthbox.getint()
            return boxresult(ARRAY, ootype.oonewarray(ARRAY, n))

        def getarrayitem(arraybox, ibox):
            array = arraybox.getref(ARRAY)
            i = ibox.getint()
            return boxresult(TYPE, array.ll_getitem_fast(i))

        def setarrayitem(arraybox, ibox, valuebox):
            array = arraybox.getref(ARRAY)
            i = ibox.getint()
            value = unwrap(TYPE, valuebox)
            array.ll_setitem_fast(i, value)

        def getarraylength(arraybox):
            array = arraybox.getref(ARRAY)
            return boxresult(ootype.Signed, array.ll_length())

        def instanceof(box):
            obj = box.getref(ootype.ROOT)
            return history.BoxInt(ootype.instanceof(obj, TYPE))

        self.create = create
        self.create_array = create_array
        self.getarrayitem = getarrayitem
        self.setarrayitem = setarrayitem
        self.getarraylength = getarraylength
        self.instanceof = instanceof
        self._is_array_of_pointers = (history.getkind(TYPE) == 'ref')
        self._is_array_of_floats = (history.getkind(TYPE) == 'float')

    def is_array_of_pointers(self):
        # for arrays, TYPE is the type of the array item.
        return self._is_array_of_pointers

    def is_array_of_floats(self):
        # for arrays, TYPE is the type of the array item.
        return self._is_array_of_floats

    def __repr__(self):
        return '<TypeDescr %s>' % self.TYPE._short_name()

class FieldDescr(OODescr):

    getfield = None
    _keys = KeyManager()

    def __init__(self, TYPE, fieldname):
        self.TYPE = TYPE
        self.fieldname = fieldname

        _, T = TYPE._lookup_field(fieldname)
        def getfield(objbox):
            obj = objbox.getref(TYPE)
            value = getattr(obj, fieldname)
            return boxresult(T, value)
        def setfield(objbox, valuebox):
            obj = objbox.getref(TYPE)
            value = unwrap(T, valuebox)
            setattr(obj, fieldname, value)

        self.getfield = getfield
        self.setfield = setfield
        self._is_pointer_field = (history.getkind(T) == 'ref')
        self._is_float_field = (history.getkind(T) == 'float')

    def sort_key(self):
        return self._keys.getkey((self.TYPE, self.fieldname))

    def is_pointer_field(self):
        return self._is_pointer_field

    def is_float_field(self):
        return self._is_float_field

    def equals(self, other):
        return self.TYPE == other.TYPE and \
            self.fieldname == other.fieldname

    def __repr__(self):
        return '<FieldDescr %r>' % self.fieldname
