/rpython/rtyper/rpbc.py
Python | 1191 lines | 1122 code | 57 blank | 12 comment | 83 complexity | 23902ca77e5a7eff1750106855ff9848 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
- import types
- from rpython.flowspace.model import FunctionGraph, Link, Block, SpaceOperation
- from rpython.annotator import model as annmodel
- from rpython.annotator.description import (
- FunctionDesc, MethodDesc, FrozenDesc, MethodOfFrozenDesc)
- from rpython.annotator.classdesc import ClassDesc
- from rpython.flowspace.model import Constant
- from rpython.annotator.argument import simple_args
- from rpython.rtyper.debug import ll_assert
- from rpython.rlib.unroll import unrolling_iterable
- from rpython.rtyper import rclass, callparse
- from rpython.rtyper.rclass import CLASSTYPE, OBJECT_VTABLE, OBJECTPTR
- from rpython.rtyper.error import TyperError
- from rpython.rtyper.lltypesystem import rffi
- from rpython.rtyper.lltypesystem import llmemory
- from rpython.rtyper.lltypesystem.lltype import (
- typeOf, Void, ForwardReference, Struct, Bool, Char, Ptr, malloc, nullptr,
- Array, Signed, cast_pointer, getfunctionptr)
- from rpython.rtyper.rmodel import (Repr, inputconst, CanBeNull, mangle,
- warning, impossible_repr)
- from rpython.tool.pairtype import pair, pairtype
- from rpython.translator.unsimplify import varoftype
- def small_cand(rtyper, s_pbc):
- if 1 < len(s_pbc.descriptions) < rtyper.getconfig().translation.withsmallfuncsets:
- callfamily = s_pbc.any_description().getcallfamily()
- llct = get_concrete_calltable(rtyper, callfamily)
- if (len(llct.uniquerows) == 1 and
- (not s_pbc.subset_of or small_cand(rtyper, s_pbc.subset_of))):
- return True
- return False
- class __extend__(annmodel.SomePBC):
- def rtyper_makerepr(self, rtyper):
- kind = self.getKind()
- if issubclass(kind, FunctionDesc):
- if len(self.descriptions) == 1 and not self.can_be_None:
- getRepr = FunctionRepr
- else:
- sample = self.any_description()
- callfamily = sample.querycallfamily()
- if callfamily and callfamily.total_calltable_size > 0:
- getRepr = FunctionsPBCRepr
- if small_cand(rtyper, self):
- getRepr = SmallFunctionSetPBCRepr
- else:
- getRepr = getFrozenPBCRepr
- elif issubclass(kind, ClassDesc):
- # user classes
- getRepr = ClassesPBCRepr
- elif issubclass(kind, MethodDesc):
- getRepr = MethodsPBCRepr
- elif issubclass(kind, FrozenDesc):
- getRepr = getFrozenPBCRepr
- elif issubclass(kind, MethodOfFrozenDesc):
- getRepr = MethodOfFrozenPBCRepr
- else:
- raise TyperError("unexpected PBC kind %r" % (kind,))
- return getRepr(rtyper, self)
- def rtyper_makekey(self):
- lst = list(self.descriptions)
- lst.sort()
- if self.subset_of:
- t = self.subset_of.rtyper_makekey()
- else:
- t = ()
- return tuple([self.__class__, self.can_be_None] + lst) + t
- # ____________________________________________________________
- class ConcreteCallTableRow(dict):
- """A row in a concrete call table."""
- @classmethod
- def from_row(cls, rtyper, row):
- concreterow = cls()
- for funcdesc, graph in row.items():
- llfn = rtyper.getcallable(graph)
- concreterow[funcdesc] = llfn
- assert len(concreterow) > 0
- # 'typeOf(llfn)' should be the same for all graphs
- concreterow.fntype = typeOf(llfn)
- return concreterow
- class LLCallTable(object):
- """A call table of a call family with low-level functions."""
- def __init__(self, table, uniquerows):
- self.table = table # (shape,index): row, maybe with duplicates
- self.uniquerows = uniquerows # list of rows, without duplicates
- def lookup(self, row):
- """A 'matching' row is one that has the same llfn, except
- that it may have more or less 'holes'."""
- for existingindex, existingrow in enumerate(self.uniquerows):
- if row.fntype != existingrow.fntype:
- continue # not the same pointer type, cannot match
- for funcdesc, llfn in row.items():
- if funcdesc in existingrow:
- if llfn != existingrow[funcdesc]:
- break # mismatch
- else:
- # potential match, unless the two rows have no common funcdesc
- merged = ConcreteCallTableRow(row)
- merged.update(existingrow)
- merged.fntype = row.fntype
- if len(merged) == len(row) + len(existingrow):
- pass # no common funcdesc, not a match
- else:
- return existingindex, merged
- raise LookupError
- def add(self, row):
- """Add a row to the table, potentially merging it with an existing row
- """
- try:
- index, merged = self.lookup(row)
- except LookupError:
- self.uniquerows.append(row) # new row
- else:
- if merged == self.uniquerows[index]:
- pass # already exactly in the table
- else:
- del self.uniquerows[index]
- self.add(merged) # add the potentially larger merged row
- def build_concrete_calltable(rtyper, callfamily):
- """Build a complete call table of a call family
- with concrete low-level function objs.
- """
- concretetable = {}
- uniquerows = []
- llct = LLCallTable(concretetable, uniquerows)
- concreterows = {}
- for shape, rows in callfamily.calltables.items():
- for index, row in enumerate(rows):
- concreterow = ConcreteCallTableRow.from_row(rtyper, row)
- concreterows[shape, index] = concreterow
- llct.add(concreterow)
- for (shape, index), row in concreterows.items():
- existingindex, biggerrow = llct.lookup(row)
- row = llct.uniquerows[existingindex]
- assert biggerrow == row
- llct.table[shape, index] = row
- if len(llct.uniquerows) == 1:
- llct.uniquerows[0].attrname = None
- else:
- for finalindex, row in enumerate(llct.uniquerows):
- row.attrname = 'variant%d' % finalindex
- return llct
- def get_concrete_calltable(rtyper, callfamily):
- """Get a complete call table of a call family
- with concrete low-level function objs.
- """
- # cache on the callfamily
- try:
- cached = rtyper.concrete_calltables[callfamily]
- except KeyError:
- llct = build_concrete_calltable(rtyper, callfamily)
- cached = llct, callfamily.total_calltable_size
- rtyper.concrete_calltables[callfamily] = cached
- else:
- llct, oldsize = cached
- if oldsize != callfamily.total_calltable_size:
- raise TyperError("call table was unexpectedly extended")
- return llct
- class FunctionReprBase(Repr):
- def __init__(self, rtyper, s_pbc):
- self.rtyper = rtyper
- self.s_pbc = s_pbc
- self.callfamily = s_pbc.any_description().getcallfamily()
- def get_s_callable(self):
- return self.s_pbc
- def get_r_implfunc(self):
- return self, 0
- def get_s_signatures(self, shape):
- funcdesc = self.s_pbc.any_description()
- return funcdesc.get_s_signatures(shape)
- def rtype_simple_call(self, hop):
- return self.call(hop)
- def rtype_call_args(self, hop):
- return self.call(hop)
- def call(self, hop):
- bk = self.rtyper.annotator.bookkeeper
- args = hop.spaceop.build_args(hop.args_s[1:])
- s_pbc = hop.args_s[0] # possibly more precise than self.s_pbc
- descs = list(s_pbc.descriptions)
- shape, index = self.callfamily.find_row(bk, descs, args, hop.spaceop)
- row_of_graphs = self.callfamily.calltables[shape][index]
- anygraph = row_of_graphs.itervalues().next() # pick any witness
- vfn = hop.inputarg(self, arg=0)
- vlist = [self.convert_to_concrete_llfn(vfn, shape, index,
- hop.llops)]
- vlist += callparse.callparse(self.rtyper, anygraph, hop)
- rresult = callparse.getrresult(self.rtyper, anygraph)
- hop.exception_is_here()
- if isinstance(vlist[0], Constant):
- v = hop.genop('direct_call', vlist, resulttype=rresult)
- else:
- vlist.append(hop.inputconst(Void, row_of_graphs.values()))
- v = hop.genop('indirect_call', vlist, resulttype=rresult)
- if hop.r_result is impossible_repr:
- return None # see test_always_raising_methods
- else:
- return hop.llops.convertvar(v, rresult, hop.r_result)
- class FunctionsPBCRepr(CanBeNull, FunctionReprBase):
- """Representation selected for a PBC of functions."""
- def __init__(self, rtyper, s_pbc):
- FunctionReprBase.__init__(self, rtyper, s_pbc)
- llct = get_concrete_calltable(self.rtyper, self.callfamily)
- self.concretetable = llct.table
- self.uniquerows = llct.uniquerows
- if len(llct.uniquerows) == 1:
- row = llct.uniquerows[0]
- self.lowleveltype = row.fntype
- else:
- # several functions, each with several specialized variants.
- # each function becomes a pointer to a Struct containing
- # pointers to its variants.
- self.lowleveltype = self.setup_specfunc()
- self.funccache = {}
- def setup_specfunc(self):
- fields = []
- for row in self.uniquerows:
- fields.append((row.attrname, row.fntype))
- kwds = {'hints': {'immutable': True}}
- return Ptr(Struct('specfunc', *fields, **kwds))
- def create_specfunc(self):
- return malloc(self.lowleveltype.TO, immortal=True)
- def get_specfunc_row(self, llop, v, c_rowname, resulttype):
- return llop.genop('getfield', [v, c_rowname], resulttype=resulttype)
- def convert_desc(self, funcdesc):
- # get the whole "column" of the call table corresponding to this desc
- try:
- return self.funccache[funcdesc]
- except KeyError:
- pass
- llfns = {}
- found_anything = False
- for row in self.uniquerows:
- if funcdesc in row:
- llfn = row[funcdesc]
- found_anything = True
- else:
- # missing entry -- need a 'null' of the type that matches
- # this row
- llfn = nullptr(row.fntype.TO)
- llfns[row.attrname] = llfn
- if len(self.uniquerows) == 1:
- if found_anything:
- result = llfn # from the loop above
- else:
- # extremely rare case, shown only sometimes by
- # test_bug_callfamily: don't emit NULL, because that
- # would be interpreted as equal to None... It should
- # never be called anyway.
- result = rffi.cast(self.lowleveltype, ~len(self.funccache))
- else:
- # build a Struct with all the values collected in 'llfns'
- result = self.create_specfunc()
- for attrname, llfn in llfns.items():
- setattr(result, attrname, llfn)
- self.funccache[funcdesc] = result
- return result
- def convert_const(self, value):
- if isinstance(value, types.MethodType) and value.im_self is None:
- value = value.im_func # unbound method -> bare function
- elif isinstance(value, staticmethod):
- value = value.__get__(42) # hackish, get the function wrapped by staticmethod
- if value is None:
- null = nullptr(self.lowleveltype.TO)
- return null
- funcdesc = self.rtyper.annotator.bookkeeper.getdesc(value)
- return self.convert_desc(funcdesc)
- def convert_to_concrete_llfn(self, v, shape, index, llop):
- """Convert the variable 'v' to a variable referring to a concrete
- low-level function. In case the call table contains multiple rows,
- 'index' and 'shape' tells which of its items we are interested in.
- """
- assert v.concretetype == self.lowleveltype
- if len(self.uniquerows) == 1:
- return v
- else:
- # 'v' is a Struct pointer, read the corresponding field
- row = self.concretetable[shape, index]
- cname = inputconst(Void, row.attrname)
- return self.get_specfunc_row(llop, v, cname, row.fntype)
- class FunctionRepr(FunctionReprBase):
- """Repr for a constant function"""
- lowleveltype = Void
- def convert_desc(self, funcdesc):
- return None
- def convert_const(self, value):
- return None
- def convert_to_concrete_llfn(self, v, shape, index, llop):
- """Convert the variable 'v' to a variable referring to a concrete
- low-level function. In case the call table contains multiple rows,
- 'index' and 'shape' tells which of its items we are interested in.
- """
- assert v.concretetype == Void
- funcdesc, = self.s_pbc.descriptions
- row_of_one_graph = self.callfamily.calltables[shape][index]
- graph = row_of_one_graph[funcdesc]
- llfn = self.rtyper.getcallable(graph)
- return inputconst(typeOf(llfn), llfn)
- def get_unique_llfn(self):
- # try to build a unique low-level function. Avoid to use
- # whenever possible! Doesn't work with specialization, multiple
- # different call sites, etc.
- funcdesc, = self.s_pbc.descriptions
- tables = [] # find the simple call in the calltable
- for shape, table in self.callfamily.calltables.items():
- if not shape[1] and not shape[2]:
- tables.append(table)
- if len(tables) != 1:
- raise TyperError("cannot pass a function with various call shapes")
- table, = tables
- graphs = []
- for row in table:
- if funcdesc in row:
- graphs.append(row[funcdesc])
- if not graphs:
- raise TyperError("cannot pass here a function that is not called")
- graph = graphs[0]
- if graphs != [graph] * len(graphs):
- raise TyperError("cannot pass a specialized function here")
- llfn = self.rtyper.getcallable(graph)
- return inputconst(typeOf(llfn), llfn)
- def get_concrete_llfn(self, s_pbc, args_s, op):
- bk = self.rtyper.annotator.bookkeeper
- funcdesc, = s_pbc.descriptions
- args = simple_args(args_s)
- with bk.at_position(None):
- graph = funcdesc.get_graph(args, op)
- llfn = self.rtyper.getcallable(graph)
- return inputconst(typeOf(llfn), llfn)
- class __extend__(pairtype(FunctionRepr, FunctionRepr)):
- def convert_from_to((r_fpbc1, r_fpbc2), v, llops):
- return v
- class __extend__(pairtype(FunctionRepr, FunctionsPBCRepr)):
- def convert_from_to((r_fpbc1, r_fpbc2), v, llops):
- return inputconst(r_fpbc2, r_fpbc1.s_pbc.const)
- class __extend__(pairtype(FunctionsPBCRepr, FunctionRepr)):
- def convert_from_to((r_fpbc1, r_fpbc2), v, llops):
- return inputconst(Void, None)
- class __extend__(pairtype(FunctionsPBCRepr, FunctionsPBCRepr)):
- def convert_from_to((r_fpbc1, r_fpbc2), v, llops):
- # this check makes sense because both source and dest repr are FunctionsPBCRepr
- if r_fpbc1.lowleveltype == r_fpbc2.lowleveltype:
- return v
- return NotImplemented
- class SmallFunctionSetPBCRepr(FunctionReprBase):
- def __init__(self, rtyper, s_pbc):
- FunctionReprBase.__init__(self, rtyper, s_pbc)
- llct = get_concrete_calltable(self.rtyper, self.callfamily)
- assert len(llct.uniquerows) == 1
- self.lowleveltype = Char
- self.pointer_repr = FunctionsPBCRepr(rtyper, s_pbc)
- self._conversion_tables = {}
- self._compression_function = None
- self._dispatch_cache = {}
- def _setup_repr(self):
- if self.s_pbc.subset_of:
- assert self.s_pbc.can_be_None == self.s_pbc.subset_of.can_be_None
- r = self.rtyper.getrepr(self.s_pbc.subset_of)
- if r is not self:
- r.setup()
- self.descriptions = r.descriptions
- self.c_pointer_table = r.c_pointer_table
- return
- self.descriptions = list(self.s_pbc.descriptions)
- if self.s_pbc.can_be_None:
- self.descriptions.insert(0, None)
- POINTER_TABLE = Array(self.pointer_repr.lowleveltype,
- hints={'nolength': True, 'immutable': True})
- pointer_table = malloc(POINTER_TABLE, len(self.descriptions),
- immortal=True)
- for i, desc in enumerate(self.descriptions):
- if desc is not None:
- pointer_table[i] = self.pointer_repr.convert_desc(desc)
- else:
- pointer_table[i] = self.pointer_repr.convert_const(None)
- self.c_pointer_table = inputconst(Ptr(POINTER_TABLE), pointer_table)
- def convert_desc(self, funcdesc):
- return chr(self.descriptions.index(funcdesc))
- def convert_const(self, value):
- if isinstance(value, types.MethodType) and value.im_self is None:
- value = value.im_func # unbound method -> bare function
- if value is None:
- assert self.descriptions[0] is None
- return chr(0)
- funcdesc = self.rtyper.annotator.bookkeeper.getdesc(value)
- return self.convert_desc(funcdesc)
- def special_uninitialized_value(self):
- return chr(0xFF)
- def dispatcher(self, shape, index, argtypes, resulttype):
- key = shape, index, tuple(argtypes), resulttype
- if key in self._dispatch_cache:
- return self._dispatch_cache[key]
- graph = self.make_dispatcher(shape, index, argtypes, resulttype)
- self.rtyper.annotator.translator.graphs.append(graph)
- ll_ret = getfunctionptr(graph)
- c_ret = self._dispatch_cache[key] = inputconst(typeOf(ll_ret), ll_ret)
- return c_ret
- def make_dispatcher(self, shape, index, argtypes, resulttype):
- inputargs = [varoftype(t) for t in [Char] + argtypes]
- startblock = Block(inputargs)
- startblock.exitswitch = inputargs[0]
- graph = FunctionGraph("dispatcher", startblock, varoftype(resulttype))
- row_of_graphs = self.callfamily.calltables[shape][index]
- links = []
- descs = list(self.s_pbc.descriptions)
- if self.s_pbc.can_be_None:
- descs.insert(0, None)
- for desc in descs:
- if desc is None:
- continue
- args_v = [varoftype(t) for t in argtypes]
- b = Block(args_v)
- llfn = self.rtyper.getcallable(row_of_graphs[desc])
- v_fn = inputconst(typeOf(llfn), llfn)
- v_result = varoftype(resulttype)
- b.operations.append(
- SpaceOperation("direct_call", [v_fn] + args_v, v_result))
- b.closeblock(Link([v_result], graph.returnblock))
- i = self.descriptions.index(desc)
- links.append(Link(inputargs[1:], b, chr(i)))
- links[-1].llexitcase = chr(i)
- startblock.closeblock(*links)
- return graph
- def call(self, hop):
- bk = self.rtyper.annotator.bookkeeper
- args = hop.spaceop.build_args(hop.args_s[1:])
- s_pbc = hop.args_s[0] # possibly more precise than self.s_pbc
- descs = list(s_pbc.descriptions)
- shape, index = self.callfamily.find_row(bk, descs, args, hop.spaceop)
- row_of_graphs = self.callfamily.calltables[shape][index]
- anygraph = row_of_graphs.itervalues().next() # pick any witness
- vlist = [hop.inputarg(self, arg=0)]
- vlist += callparse.callparse(self.rtyper, anygraph, hop)
- rresult = callparse.getrresult(self.rtyper, anygraph)
- hop.exception_is_here()
- v_dispatcher = self.dispatcher(shape, index,
- [v.concretetype for v in vlist[1:]], rresult.lowleveltype)
- v_result = hop.genop('direct_call', [v_dispatcher] + vlist,
- resulttype=rresult)
- return hop.llops.convertvar(v_result, rresult, hop.r_result)
- def rtype_bool(self, hop):
- if not self.s_pbc.can_be_None:
- return inputconst(Bool, True)
- else:
- v1, = hop.inputargs(self)
- return hop.genop('char_ne', [v1, inputconst(Char, '\000')],
- resulttype=Bool)
- class __extend__(pairtype(SmallFunctionSetPBCRepr, FunctionRepr)):
- def convert_from_to((r_set, r_ptr), v, llops):
- return inputconst(Void, None)
- class __extend__(pairtype(SmallFunctionSetPBCRepr, FunctionsPBCRepr)):
- def convert_from_to((r_set, r_ptr), v, llops):
- assert v.concretetype is Char
- v_int = llops.genop('cast_char_to_int', [v], resulttype=Signed)
- return llops.genop('getarrayitem', [r_set.c_pointer_table, v_int],
- resulttype=r_ptr.lowleveltype)
- def compression_function(r_set):
- if r_set._compression_function is None:
- table = []
- for i, p in enumerate(r_set.c_pointer_table.value):
- table.append((chr(i), p))
- last_c, last_p = table[-1]
- unroll_table = unrolling_iterable(table[:-1])
- def ll_compress(fnptr):
- for c, p in unroll_table:
- if fnptr == p:
- return c
- else:
- ll_assert(fnptr == last_p, "unexpected function pointer")
- return last_c
- r_set._compression_function = ll_compress
- return r_set._compression_function
- class __extend__(pairtype(FunctionRepr, SmallFunctionSetPBCRepr)):
- def convert_from_to((r_ptr, r_set), v, llops):
- desc, = r_ptr.s_pbc.descriptions
- return inputconst(Char, r_set.convert_desc(desc))
- class __extend__(pairtype(FunctionsPBCRepr, SmallFunctionSetPBCRepr)):
- def convert_from_to((r_ptr, r_set), v, llops):
- ll_compress = compression_function(r_set)
- return llops.gendirectcall(ll_compress, v)
- class __extend__(pairtype(FunctionReprBase, FunctionReprBase)):
- def rtype_is_((robj1, robj2), hop):
- if hop.s_result.is_constant():
- return inputconst(Bool, hop.s_result.const)
- s_pbc = annmodel.unionof(robj1.s_pbc, robj2.s_pbc)
- r_pbc = hop.rtyper.getrepr(s_pbc)
- v1, v2 = hop.inputargs(r_pbc, r_pbc)
- assert v1.concretetype == v2.concretetype
- if v1.concretetype == Char:
- return hop.genop('char_eq', [v1, v2], resulttype=Bool)
- elif isinstance(v1.concretetype, Ptr):
- return hop.genop('ptr_eq', [v1, v2], resulttype=Bool)
- else:
- raise TyperError("unknown type %r" % (v1.concretetype,))
- def conversion_table(r_from, r_to):
- if r_to in r_from._conversion_tables:
- return r_from._conversion_tables[r_to]
- else:
- t = malloc(Array(Char, hints={'nolength': True, 'immutable': True}),
- len(r_from.descriptions), immortal=True)
- l = []
- for i, d in enumerate(r_from.descriptions):
- if d in r_to.descriptions:
- j = r_to.descriptions.index(d)
- l.append(j)
- t[i] = chr(j)
- else:
- l.append(None)
- if l == range(len(r_from.descriptions)):
- r = None
- else:
- r = inputconst(typeOf(t), t)
- r_from._conversion_tables[r_to] = r
- return r
- class __extend__(pairtype(SmallFunctionSetPBCRepr, SmallFunctionSetPBCRepr)):
- def convert_from_to((r_from, r_to), v, llops):
- c_table = conversion_table(r_from, r_to)
- if c_table:
- assert v.concretetype is Char
- v_int = llops.genop('cast_char_to_int', [v],
- resulttype=Signed)
- return llops.genop('getarrayitem', [c_table, v_int],
- resulttype=Char)
- else:
- return v
- def getFrozenPBCRepr(rtyper, s_pbc):
- descs = list(s_pbc.descriptions)
- assert len(descs) >= 1
- if len(descs) == 1 and not s_pbc.can_be_None:
- return SingleFrozenPBCRepr(descs[0])
- else:
- access = descs[0].queryattrfamily()
- for desc in descs[1:]:
- access1 = desc.queryattrfamily()
- if access1 is not access:
- try:
- return rtyper.pbc_reprs['unrelated']
- except KeyError:
- result = MultipleUnrelatedFrozenPBCRepr(rtyper)
- rtyper.pbc_reprs['unrelated'] = result
- return result
- try:
- return rtyper.pbc_reprs[access]
- except KeyError:
- result = MultipleFrozenPBCRepr(rtyper, access)
- rtyper.pbc_reprs[access] = result
- rtyper.add_pendingsetup(result)
- return result
- class SingleFrozenPBCRepr(Repr):
- """Representation selected for a single non-callable pre-built constant."""
- lowleveltype = Void
- def __init__(self, frozendesc):
- self.frozendesc = frozendesc
- def rtype_getattr(_, hop):
- if not hop.s_result.is_constant():
- raise TyperError("getattr on a constant PBC returns a non-constant")
- return hop.inputconst(hop.r_result, hop.s_result.const)
- def convert_desc(self, frozendesc):
- assert frozendesc is self.frozendesc
- return object() # lowleveltype is Void
- def convert_const(self, value):
- return None
- def getstr(self):
- return str(self.frozendesc)
- getstr._annspecialcase_ = 'specialize:memo'
- def ll_str(self, x):
- return self.getstr()
- class MultipleFrozenPBCReprBase(CanBeNull, Repr):
- def convert_const(self, pbc):
- if pbc is None:
- return self.null_instance()
- frozendesc = self.rtyper.annotator.bookkeeper.getdesc(pbc)
- return self.convert_desc(frozendesc)
- class MultipleUnrelatedFrozenPBCRepr(MultipleFrozenPBCReprBase):
- """For a SomePBC of frozen PBCs that have no common access set.
- The only possible operation on such a thing is comparison with 'is'."""
- lowleveltype = llmemory.Address
- EMPTY = Struct('pbc', hints={'immutable': True})
- def __init__(self, rtyper):
- self.rtyper = rtyper
- self.converted_pbc_cache = {}
- def convert_desc(self, frozendesc):
- try:
- return self.converted_pbc_cache[frozendesc]
- except KeyError:
- r = self.rtyper.getrepr(annmodel.SomePBC([frozendesc]))
- if r.lowleveltype is Void:
- # must create a new empty structure, as a placeholder
- pbc = self.create_instance()
- else:
- pbc = r.convert_desc(frozendesc)
- convpbc = self.convert_pbc(pbc)
- self.converted_pbc_cache[frozendesc] = convpbc
- return convpbc
- def convert_pbc(self, pbcptr):
- return llmemory.fakeaddress(pbcptr)
- def create_instance(self):
- return malloc(self.EMPTY, immortal=True)
- def null_instance(self):
- return llmemory.Address._defl()
- def rtype_getattr(_, hop):
- if not hop.s_result.is_constant():
- raise TyperError("getattr on a constant PBC returns a non-constant")
- return hop.inputconst(hop.r_result, hop.s_result.const)
- class __extend__(pairtype(MultipleUnrelatedFrozenPBCRepr,
- MultipleUnrelatedFrozenPBCRepr),
- pairtype(MultipleUnrelatedFrozenPBCRepr,
- SingleFrozenPBCRepr),
- pairtype(SingleFrozenPBCRepr,
- MultipleUnrelatedFrozenPBCRepr)):
- def rtype_is_((robj1, robj2), hop):
- if isinstance(robj1, MultipleUnrelatedFrozenPBCRepr):
- r = robj1
- else:
- r = robj2
- vlist = hop.inputargs(r, r)
- return hop.genop('adr_eq', vlist, resulttype=Bool)
- class MultipleFrozenPBCRepr(MultipleFrozenPBCReprBase):
- """For a SomePBC of frozen PBCs with a common attribute access set."""
- def __init__(self, rtyper, access_set):
- self.rtyper = rtyper
- self.access_set = access_set
- self.pbc_type = ForwardReference()
- self.lowleveltype = Ptr(self.pbc_type)
- self.pbc_cache = {}
- def _setup_repr(self):
- llfields = self._setup_repr_fields()
- kwds = {'hints': {'immutable': True}}
- self.pbc_type.become(Struct('pbc', *llfields, **kwds))
- def create_instance(self):
- return malloc(self.pbc_type, immortal=True)
- def null_instance(self):
- return nullptr(self.pbc_type)
- def getfield(self, vpbc, attr, llops):
- mangled_name, r_value = self.fieldmap[attr]
- cmangledname = inputconst(Void, mangled_name)
- return llops.genop('getfield', [vpbc, cmangledname],
- resulttype=r_value)
- def _setup_repr_fields(self):
- fields = []
- self.fieldmap = {}
- if self.access_set is not None:
- attrlist = self.access_set.attrs.keys()
- attrlist.sort()
- for attr in attrlist:
- s_value = self.access_set.attrs[attr]
- r_value = self.rtyper.getrepr(s_value)
- mangled_name = mangle('pbc', attr)
- fields.append((mangled_name, r_value.lowleveltype))
- self.fieldmap[attr] = mangled_name, r_value
- return fields
- def convert_desc(self, frozendesc):
- if (self.access_set is not None and
- frozendesc not in self.access_set.descs):
- raise TyperError("not found in PBC access set: %r" % (frozendesc,))
- try:
- return self.pbc_cache[frozendesc]
- except KeyError:
- self.setup()
- result = self.create_instance()
- self.pbc_cache[frozendesc] = result
- for attr, (mangled_name, r_value) in self.fieldmap.items():
- if r_value.lowleveltype is Void:
- continue
- try:
- thisattrvalue = frozendesc.attrcache[attr]
- except KeyError:
- if frozendesc.warn_missing_attribute(attr):
- warning("Desc %r has no attribute %r" % (frozendesc, attr))
- continue
- llvalue = r_value.convert_const(thisattrvalue)
- setattr(result, mangled_name, llvalue)
- return result
- def rtype_getattr(self, hop):
- if hop.s_result.is_constant():
- return hop.inputconst(hop.r_result, hop.s_result.const)
- attr = hop.args_s[1].const
- vpbc, vattr = hop.inputargs(self, Void)
- v_res = self.getfield(vpbc, attr, hop.llops)
- mangled_name, r_res = self.fieldmap[attr]
- return hop.llops.convertvar(v_res, r_res, hop.r_result)
- class __extend__(pairtype(MultipleFrozenPBCRepr,
- MultipleUnrelatedFrozenPBCRepr)):
- def convert_from_to((robj1, robj2), v, llops):
- return llops.genop('cast_ptr_to_adr', [v], resulttype=llmemory.Address)
- class __extend__(pairtype(MultipleFrozenPBCRepr, MultipleFrozenPBCRepr)):
- def convert_from_to((r_pbc1, r_pbc2), v, llops):
- if r_pbc1.access_set == r_pbc2.access_set:
- return v
- return NotImplemented
- class __extend__(pairtype(SingleFrozenPBCRepr, MultipleFrozenPBCRepr)):
- def convert_from_to((r_pbc1, r_pbc2), v, llops):
- frozendesc1 = r_pbc1.frozendesc
- access = frozendesc1.queryattrfamily()
- if access is r_pbc2.access_set:
- value = r_pbc2.convert_desc(frozendesc1)
- lltype = r_pbc2.lowleveltype
- return Constant(value, lltype)
- return NotImplemented
- class __extend__(pairtype(MultipleFrozenPBCReprBase,
- SingleFrozenPBCRepr)):
- def convert_from_to((r_pbc1, r_pbc2), v, llops):
- return inputconst(Void, r_pbc2.frozendesc)
- class MethodOfFrozenPBCRepr(Repr):
- """Representation selected for a PBC of method object(s) of frozen PBCs.
- It assumes that all methods are the same function bound to different PBCs.
- The low-level representation can then be a pointer to that PBC."""
- def __init__(self, rtyper, s_pbc):
- self.rtyper = rtyper
- self.funcdesc = s_pbc.any_description().funcdesc
- # a hack to force the underlying function to show up in call_families
- # (generally not needed, as normalizecalls() should ensure this,
- # but needed for bound methods that are ll helpers)
- # XXX sort this out
- #call_families = rtyper.annotator.getpbccallfamilies()
- #call_families.find((None, self.function))
- if s_pbc.can_be_none():
- raise TyperError("unsupported: variable of type "
- "method-of-frozen-PBC or None")
- im_selves = []
- for desc in s_pbc.descriptions:
- if desc.funcdesc is not self.funcdesc:
- raise TyperError(
- "You can't mix a set of methods on a frozen PBC in "
- "RPython that are different underlying functions")
- im_selves.append(desc.frozendesc)
- self.s_im_self = annmodel.SomePBC(im_selves)
- self.r_im_self = rtyper.getrepr(self.s_im_self)
- self.lowleveltype = self.r_im_self.lowleveltype
- def get_s_callable(self):
- return annmodel.SomePBC([self.funcdesc])
- def get_r_implfunc(self):
- r_func = self.rtyper.getrepr(self.get_s_callable())
- return r_func, 1
- def convert_desc(self, mdesc):
- if mdesc.funcdesc is not self.funcdesc:
- raise TyperError("not a method bound on %r: %r" % (self.funcdesc,
- mdesc))
- return self.r_im_self.convert_desc(mdesc.frozendesc)
- def convert_const(self, method):
- mdesc = self.rtyper.annotator.bookkeeper.getdesc(method)
- return self.convert_desc(mdesc)
- def rtype_simple_call(self, hop):
- return self.redispatch_call(hop, call_args=False)
- def rtype_call_args(self, hop):
- return self.redispatch_call(hop, call_args=True)
- def redispatch_call(self, hop, call_args):
- # XXX obscure, try to refactor...
- s_function = annmodel.SomePBC([self.funcdesc])
- hop2 = hop.copy()
- hop2.args_s[0] = self.s_im_self # make the 1st arg stand for 'im_self'
- hop2.args_r[0] = self.r_im_self # (same lowleveltype as 'self')
- if isinstance(hop2.args_v[0], Constant):
- boundmethod = hop2.args_v[0].value
- hop2.args_v[0] = Constant(boundmethod.im_self)
- if call_args:
- hop2.swap_fst_snd_args()
- _, s_shape = hop2.r_s_popfirstarg() # temporarely remove shape
- adjust_shape(hop2, s_shape)
- # a marker that would crash if actually used...
- c = Constant("obscure-don't-use-me")
- hop2.v_s_insertfirstarg(c, s_function) # insert 'function'
- # now hop2 looks like simple_call(function, self, args...)
- return hop2.dispatch()
- class __extend__(pairtype(MethodOfFrozenPBCRepr, MethodOfFrozenPBCRepr)):
- def convert_from_to((r_from, r_to), v, llops):
- return pair(r_from.r_im_self, r_to.r_im_self).convert_from_to(v, llops)
- # ____________________________________________________________
- class ClassesPBCRepr(Repr):
- """Representation selected for a PBC of class(es)."""
- def __init__(self, rtyper, s_pbc):
- self.rtyper = rtyper
- self.s_pbc = s_pbc
- #if s_pbc.can_be_None:
- # raise TyperError("unsupported: variable of type "
- # "class-pointer or None")
- if s_pbc.is_constant():
- self.lowleveltype = Void
- else:
- self.lowleveltype = self.getlowleveltype()
- def get_access_set(self, attrname):
- """Return the ClassAttrFamily corresponding to accesses to 'attrname'
- and the ClassRepr of the class which stores this attribute in
- its vtable.
- """
- classdescs = list(self.s_pbc.descriptions)
- access = classdescs[0].queryattrfamily(attrname)
- for classdesc in classdescs[1:]:
- access1 = classdesc.queryattrfamily(attrname)
- assert access1 is access # XXX not implemented
- if access is None:
- raise rclass.MissingRTypeAttribute(attrname)
- commonbase = access.commonbase
- class_repr = rclass.getclassrepr(self.rtyper, commonbase)
- return access, class_repr
- def convert_desc(self, desc):
- if desc not in self.s_pbc.descriptions:
- raise TyperError("%r not in %r" % (desc, self))
- if self.lowleveltype is Void:
- return None
- subclassdef = desc.getuniqueclassdef()
- r_subclass = rclass.getclassrepr(self.rtyper, subclassdef)
- return r_subclass.getruntime(self.lowleveltype)
- def convert_const(self, cls):
- if cls is None:
- if self.lowleveltype is Void:
- return None
- else:
- T = self.lowleveltype
- return nullptr(T.TO)
- bk = self.rtyper.annotator.bookkeeper
- classdesc = bk.getdesc(cls)
- return self.convert_desc(classdesc)
- def rtype_getattr(self, hop):
- if hop.s_result.is_constant():
- return hop.inputconst(hop.r_result, hop.s_result.const)
- else:
- attr = hop.args_s[1].const
- if attr == '__name__':
- from rpython.rtyper.lltypesystem import rstr
- class_repr = self.rtyper.rootclass_repr
- vcls, vattr = hop.inputargs(class_repr, Void)
- cname = inputconst(Void, 'name')
- return hop.genop('getfield', [vcls, cname],
- resulttype = Ptr(rstr.STR))
- access_set, class_repr = self.get_access_set(attr)
- vcls, vattr = hop.inputargs(class_repr, Void)
- v_res = class_repr.getpbcfield(vcls, access_set, attr, hop.llops)
- s_res = access_set.s_value
- r_res = self.rtyper.getrepr(s_res)
- return hop.llops.convertvar(v_res, r_res, hop.r_result)
- def replace_class_with_inst_arg(self, hop, v_inst, s_inst, call_args):
- hop2 = hop.copy()
- hop2.r_s_popfirstarg() # discard the class pointer argument
- if call_args:
- _, s_shape = hop2.r_s_popfirstarg() # temporarely remove shape
- hop2.v_s_insertfirstarg(v_inst, s_inst) # add 'instance'
- adjust_shape(hop2, s_shape)
- else:
- hop2.v_s_insertfirstarg(v_inst, s_inst) # add 'instance'
- return hop2
- def rtype_simple_call(self, hop):
- return self.redispatch_call(hop, call_args=False)
- def rtype_call_args(self, hop):
- return self.redispatch_call(hop, call_args=True)
- def redispatch_call(self, hop, call_args):
- s_instance = hop.s_result
- r_instance = hop.r_result
- if len(self.s_pbc.descriptions) == 1:
- # instantiating a single class
- if self.lowleveltype is not Void:
- assert 0, "XXX None-or-1-class instantation not implemented"
- assert isinstance(s_instance, annmodel.SomeInstance)
- classdef = s_instance.classdef
- s_init = classdef.classdesc.s_read_attribute('__init__')
- v_init = Constant("init-func-dummy") # this value not really used
- if (isinstance(s_init, annmodel.SomeImpossibleValue) and
- classdef.classdesc.is_exception_class() and
- classdef.has_no_attrs()):
- # special case for instanciating simple built-in
- # exceptions: always return the same prebuilt instance,
- # and ignore any arguments passed to the contructor.
- r_instance = rclass.getinstancerepr(hop.rtyper, classdef)
- example = r_instance.get_reusable_prebuilt_instance()
- hop.exception_cannot_occur()
- return hop.inputconst(r_instance.lowleveltype, example)
- v_instance = rclass.rtype_new_instance(hop.rtyper, classdef,
- hop.llops, hop)
- if isinstance(v_instance, tuple):
- v_instance, must_call_init = v_instance
- if not must_call_init:
- return v_instance
- else:
- # instantiating a class from multiple possible classes
- vtypeptr = hop.inputarg(self, arg=0)
- try:
- access_set, r_class = self.get_access_set('__init__')
- except rclass.MissingRTypeAttribute:
- s_init = annmodel.s_ImpossibleValue
- else:
- s_init = access_set.s_value
- v_init = r_class.getpbcfield(vtypeptr, access_set, '__init__',
- hop.llops)
- v_instance = self._instantiate_runtime_class(hop, vtypeptr, r_instance)
- if isinstance(s_init, annmodel.SomeImpossibleValue):
- assert hop.nb_args == 1, ("arguments passed to __init__, "
- "but no __init__!")
- hop.exception_cannot_occur()
- else:
- hop2 = self.replace_class_with_inst_arg(
- hop, v_instance, s_instance, call_args)
- hop2.v_s_insertfirstarg(v_init, s_init) # add 'initfunc'
- hop2.s_result = annmodel.s_None
- hop2.r_result = self.rtyper.getrepr(hop2.s_result)
- # now hop2 looks like simple_call(initfunc, instance, args...)
- hop2.dispatch()
- return v_instance
- def _instantiate_runtime_class(self, hop, vtypeptr, r_instance):
- graphs = []
- for desc in self.s_pbc.descriptions:
- classdef = desc.getclassdef(None)
- assert hasattr(classdef, 'my_instantiate_graph')
- graphs.append(classdef.my_instantiate_graph)
- c_graphs = hop.inputconst(Void, graphs)
- #
- # "my_instantiate = typeptr.instantiate"
- c_name = hop.inputconst(Void, 'instantiate')
- v_instantiate = hop.genop('getfield', [vtypeptr, c_name],
- resulttype=OBJECT_VTABLE.instantiate)
- # "my_instantiate()"
- v_inst = hop.genop('indirect_call', [v_instantiate, c_graphs],
- resulttype=OBJECTPTR)
- return hop.genop('cast_pointer', [v_inst], resulttype=r_instance)
- def getlowleveltype(self):
- return CLASSTYPE
- def get_ll_hash_function(self):
- return ll_cls_hash
- get_ll_fasthash_function = get_ll_hash_function
- def get_ll_eq_function(self):
- return None
- def ll_str(self, ptr):
- cls = cast_pointer(CLASSTYPE, ptr)
- return cls.name
- def ll_cls_hash(cls):
- if not cls:
- return 0
- else:
- return cls.hash
- class __extend__(pairtype(ClassesPBCRepr, rclass.ClassRepr)):
- def convert_from_to((r_clspbc, r_cls), v, llops):
- # turn a PBC of classes to a standard pointer-to-vtable class repr
- if r_clspbc.lowleveltype == r_cls.lowleveltype:
- return v
- if r_clspbc.lowleveltype is Void:
- return inputconst(r_cls, r_clspbc.s_pbc.const)
- # convert from ptr-to-object-vtable to ptr-to-more-precise-vtable
- return r_cls.fromclasstype(v, llops)
- class __extend__(pairtype(ClassesPBCRepr, ClassesPBCRepr)):
- def convert_from_to((r_clspbc1, r_clspbc2), v, llops):
- # this check makes sense because both source and dest repr are ClassesPBCRepr
- if r_clspbc1.lowleveltype == r_clspbc2.lowleveltype:
- return v
- if r_clspbc1.lowleveltype is Void:
- return inputconst(r_clspbc2, r_clspbc1.s_pbc.const)
- if r_clspbc2.lowleveltype is Void:
- return inputconst(Void, r_clspbc2.s_pbc.const)
- return NotImplemented
- def adjust_shape(hop2, s_shape):
- new_shape = (s_shape.const[0]+1,) + s_shape.const[1:]
- c_shape = Constant(new_shape)
- s_shape = hop2.rtyper.annotator.bookkeeper.immutablevalue(new_shape)
- hop2.v_s_insertfirstarg(c_shape, s_shape) # reinsert adjusted shape
- class MethodsPBCRepr(Repr):
- """Representation selected for a PBC of MethodDescs.
- It assumes that all the methods come from the same name and have
- been read from instances with a common base."""
- def __init__(self, rtyper, s_pbc):
- self.rtyper = rtyper
- self.s_pbc = s_pbc
- mdescs = list(s_pbc.descriptions)
- methodname = mdescs[0].name
- classdef = mdescs[0].selfclassdef
- flags = mdescs[0].flags
- for mdesc in mdescs[1:]:
- if mdesc.name != methodname:
- raise TyperError("cannot find a unique name under which the "
- "methods can be found: %r" % (
- mdescs,))
- if mdesc.flags != flags:
- raise TyperError("inconsistent 'flags': %r versus %r" % (
- mdesc.flags, flags))
- classdef = classdef.commonbase(mdesc.selfclassdef)
- if classdef is None:
- raise TyperError("mixing methods coming from instances of "
- "classes with no common base: %r" % (mdescs,))
- self.methodname = methodname
- self.classdef = classdef.get_owner(methodname)
- # the low-level representation is just the bound 'self' argument.
- self.s_im_self = annmodel.SomeInstance(self.classdef, flags=flags)
- self.r_im_self = rclass.getinstancerepr(rtyper, self.classdef)
- self.lowleveltype = self.r_im_self.lowleveltype
- def convert_const(self, method):
- if getattr(method, 'im_func', None) is None:
- raise TyperError("not a bound method: %r" % method)
- return self.r_im_self.convert_const(method.im_self)
- def get_r_implfunc(self):
- r_class = self.r_im_self.rclass
- mangled_name, r_func = r_class.clsfields[self.methodname]
- return r_func, 1
- def get_s_callable(self):
- return self.s_pbc
- def get_method_from_instance(self, r_inst, v_inst, llops):
- # The 'self' might have to be cast to a parent class
- # (as shown for example in test_rclass/test_method_both_A_and_B)
- return llops.convertvar(v_inst, r_inst, self.r_im_self)
- def add_instance_arg_to_hop(self, hop, call_args):
- hop2 = hop.copy()
- hop2.args_s[0] = self.s_im_self # make the 1st arg stand for 'im_self'
- hop2.args_r[0] = self.r_im_self # (same lowleveltype as 'self')
- if call_args:
- hop2.swap_fst_snd_args()
- _, s_shape = hop2.r_s_popfirstarg()
- adjust_shape(hop2, s_shape)
- return hop2
- def rtype_simple_call(self, hop):
- return self.redispatch_call(hop, call_args=False)
- def rtype_call_args(self, hop):
- return self.redispatch_call(hop, call_args=True)
- def redispatch_call(self, hop, call_args):
- r_class = self.r_im_self.rclass
- mangled_name, r_func = r_class.clsfields[self.methodname]
- assert isinstance(r_func, FunctionReprBase)
- # s_func = r_func.s_pbc -- not precise enough, see
- # test_precise_method_call_1. Build a more precise one...
- funcdescs = [desc.funcdesc for desc in hop.args_s[0].descriptions]
- s_func = annmodel.SomePBC(funcdescs, subset_of=r_func.s_pbc)
- v_im_self = hop.inputarg(self, arg=0)
- v_cls = self.r_im_self.getfield(v_im_self, '__class__', hop.llops)
- v_func = r_class.getclsfield(v_cls, self.methodname, hop.llops)
- hop2 = self.add_instance_arg_to_hop(hop, call_args)
- hop2.v_s_insertfirstarg(v_func, s_func) # insert 'function'
- if (type(hop2.args_r[0]) is SmallFunctionSetPBCRepr and
- type(r_func) is FunctionsPBCRepr):
- hop2.args_r[0] = FunctionsPBCRepr(self.rtyper, s_func)
- else:
- hop2.args_v[0] = hop2.llops.convertvar(
- hop2.args_v[0], r_func, hop2.args_r[0])
- # now hop2 looks like simple_call(function, self, args...)
- return hop2.dispatch()