[pypy-svn] r6360 - in pypy/trunk/src/pypy/translator: . test

arigo at codespeak.net arigo at codespeak.net
Wed Sep 8 15:55:54 CEST 2004


Author: arigo
Date: Wed Sep  8 15:55:53 2004
New Revision: 6360

Added:
   pypy/trunk/src/pypy/translator/genc_op.py
Modified:
   pypy/trunk/src/pypy/translator/genc.h
   pypy/trunk/src/pypy/translator/genc.py
   pypy/trunk/src/pypy/translator/genc_typeset.py
   pypy/trunk/src/pypy/translator/test/test_ctrans.py
   pypy/trunk/src/pypy/translator/translator.py
   pypy/trunk/src/pypy/translator/typer.py
Log:
Large check-in.  The main addition is a set of classes in genc_op.py that
correspond to the individual kinds of operations, and how to write them in C.  
Each of these classes replace a 'def writer(...)' function.  This is much
cleaner because the 'writer' functions were increasingly populated with
optional attributes to control the operation.

Less important changes:

* Automatic chaining of conversions.  Was needed to cleanly convert tuples
  item by item.
* Avoid generating two .c modules with exactly the same name during the same
  session, because we cannot reload the .so/.pyd extension.
* Python functions can return and be called with a return value spread over
  several LLVars (e.g. when returning a tuple).
* Cleaned up the generation of the entry_point() in the C module.



Modified: pypy/trunk/src/pypy/translator/genc.h
==============================================================================
--- pypy/trunk/src/pypy/translator/genc.h	(original)
+++ pypy/trunk/src/pypy/translator/genc.h	Wed Sep  8 15:55:53 2004
@@ -51,6 +51,13 @@
 #define OP_DELATTR_oov(x,y,err)     if ((PyObject_SetAttr(x,y,NULL))<0)goto err;
 #define OP_NEWSLICE_oooo(x,y,z,r,err)  if (!(r=PySlice_New(x,y,z)))    goto err;
 
+/* temporary hack */
+#define OP_GETITEM_ooi(x,y,r,err)   {                           \
+  PyObject* o = PyObject_GetItem(x,y);                          \
+  if (!o) goto err;                                             \
+  if ((r=PyInt_AsLong(o)) == -1 && PyErr_Occurred()) goto err;  \
+}
+
 
 /*** conversions ***/
 
@@ -58,9 +65,9 @@
 #define convert_so(c,l,r,err) if (!(r=PyString_FromStringAndSize(c,l)))goto err;
 #define convert_vo(r)         r = Py_None; Py_INCREF(r);
 
-#define convert_oi(x,r,err)   if ((r=PyInt_AsLong(x)) == -1             \
-                                  && PyErr_Occurred()) goto err;
-
+/*#define convert_oi(x,r,err)   if ((r=PyInt_AsLong(x)) == -1             \
+ *                                  && PyErr_Occurred()) goto err;
+ * -- should be done differently */
 
 /*** tests ***/
 
@@ -77,11 +84,6 @@
 
 /*** misc ***/
 
-#define return_i(n)             return n;
-#define return_o(o)             return o;
-#define returnerr_i()           return -1;
-#define returnerr_o()           return NULL;
-
 #define OP_EXCEPTION_ov(x)    /* XXX exceptions not implemented */
 
 #define OP_ALLOC_AND_SET_ioo(l,o,r,err)  {              \

Modified: pypy/trunk/src/pypy/translator/genc.py
==============================================================================
--- pypy/trunk/src/pypy/translator/genc.py	(original)
+++ pypy/trunk/src/pypy/translator/genc.py	Wed Sep  8 15:55:53 2004
@@ -4,27 +4,36 @@
 """
 from __future__ import generators
 import autopath, os
-from pypy.translator.typer import LLFunction, LLOp, LLConst
+from pypy.objspace.flow.model import Variable, Constant, SpaceOperation
+from pypy.objspace.flow.model import FunctionGraph, Block, Link
+from pypy.translator.typer import LLFunction, LLOp, LLVar, LLConst
 from pypy.translator.classtyper import LLClass
-from pypy.translator.genc_typeset import CTypeSet, consts_used
+from pypy.translator.genc_typeset import CTypeSet
+from pypy.translator.genc_op import ERROR_RETVAL
 
 # ____________________________________________________________
 
+def uniquemodulename(name, SEEN={}):
+    # never reuse the same module name within a Python session!
+    i = 0
+    while True:
+        i += 1
+        result = '%s_%d' % (name, i)
+        if result not in SEEN:
+            SEEN[result] = True
+            return result
+
+
 class GenC:
+    MODNAMES = {}
 
     def __init__(self, f, translator, modname=None):
         self.f = f
         self.translator = translator
-        self.modname = modname or translator.functions[0].__name__
+        self.modname = (modname or
+                        uniquemodulename(translator.functions[0].__name__))
         if translator.annotator:
             bindings = translator.annotator.bindings.copy()
-            # for simplicity, force the entry point's return type to be
-            # 'PyObject*'
-            try:
-                entrypoint = translator.functions[0]
-                del bindings[translator.flowgraphs[entrypoint].getreturnvar()]
-            except KeyError:
-                pass
         else:
             bindings = {}
         self.typeset = CTypeSet(self, bindings)
@@ -33,6 +42,7 @@
         self.llfunctions = {}; self.functionslist = []
         self.build_llclasses()
         self.build_llfunctions()
+        self.build_llentrypoint()
         self.gen_source()
 
     def gen_source(self):
@@ -64,7 +74,7 @@
         # entry point
         print >> f, self.C_SEP
         print >> f, self.C_ENTRYPOINT_HEADER % info
-        self.gen_entrypoint(self.translator.functions[0])
+        self.gen_entrypoint()
         print >> f, self.C_ENTRYPOINT_FOOTER % info
 
         # footer
@@ -104,18 +114,49 @@
             self.functionslist.append(llfunc)
             n += 1
 
+    def build_llentrypoint(self):
+        # create a LLFunc that calls the entry point function and returns
+        # whatever it returns, but converted to PyObject*.
+        main = self.translator.functions[0]
+        llmain = self.llfunctions[main]
+        inputargs = llmain.graph.getargs()
+        b = Block(inputargs)
+        v1 = Variable()
+        b.operations.append(SpaceOperation('simple_call',
+                                           [Constant(main)] + inputargs,
+                                           v1))
+        # finally, return v1
+        graph = FunctionGraph('entry_point', b)
+        b.closeblock(Link([v1], graph.returnblock))
+        llfunc = LLFunction(self.typeset, graph.name, graph)
+        self.functionslist.append(llfunc)
+
+    def get_llfunc_header(self, llfunc):
+        llargs, llret = llfunc.ll_header()
+        if len(llret) == 0:
+            retlltype = None
+        elif len(llret) == 1:
+            retlltype = llret[0].type
+        else:
+            # if there is more than one return LLVar, only the first one is
+            # returned and the other ones are returned via ptr output args
+            retlltype = llret[0].type
+            llargs += [LLVar(a.type+'*', 'output_'+a.name) for a in llret[1:]]
+        return llargs, retlltype
+
     def cfunction_header(self, llfunc):
-        llargs, rettype = llfunc.ll_header()
+        llargs, rettype = self.get_llfunc_header(llfunc)
         l = ['%s %s' % (a.type, a.name) for a in llargs]
         l = l or ['void']
         return 'static %s %s(%s)' % (rettype or 'int',
                                      llfunc.name,
                                      ', '.join(l))
 
-    def gen_entrypoint(self, func):
+    def gen_entrypoint(self):
         f = self.f
-        llfunc = self.llfunctions[func]
-        llargs, rettype = llfunc.ll_header()
+        llfunc = self.functionslist[-1]
+        llargs, rettype = self.get_llfunc_header(llfunc)
+        assert llfunc.name == 'entry_point', llfunc.name
         assert rettype == 'PyObject*', rettype
         l = []
         l2 = []
@@ -132,20 +173,22 @@
         l.insert(0, '"' + ''.join(formatstr) + '"')
         print >> f, '\tif (!PyArg_ParseTuple(args, %s))' % ', '.join(l)
         print >> f, '\t\treturn NULL;'
-        print >> f, '\treturn %s(%s);' % (llfunc.name, ', '.join(l2))
+        print >> f, '\treturn entry_point(%s);' % (', '.join(l2))
 
     def gen_cfunction(self, llfunc):
         f = self.f
 
         # generate the body of the function
-        body = list(llfunc.ll_body())
+        llargs, rettype = self.get_llfunc_header(llfunc)
+        error_retval = LLConst(rettype, ERROR_RETVAL[rettype])
+        body = list(llfunc.ll_body([error_retval]))
 
         # print the declaration of the new global constants needed by
         # the current function
         to_declare = []
         for line in body:
             if isinstance(line, LLOp):
-                for a in line.args + consts_used(line.name):
+                for a in line.using():
                     if isinstance(a, LLConst) and a.to_declare:
                         to_declare.append(a)
                         if a.initexpr:
@@ -163,11 +206,10 @@
         print >> f, '{'
 
         # collect and print all the local variables from the body
-        llargs, rettype = llfunc.ll_header()
-        lllocals = llargs[:]
+        lllocals = []
         for line in body:
             if isinstance(line, LLOp):
-                lllocals += line.args
+                lllocals += line.using()
         seen = {}
         for a in llargs:
             seen[a] = True
@@ -181,67 +223,16 @@
         # print the body
         for line in body:
             if isinstance(line, LLOp):
-                writer = line.name
-                if isinstance(writer, str):   # special low-level operation
-                    meth = getattr(self, 'lloperation_' + writer)
-                    code = meth(line)
-                else:
-                    # line.name is actually not a string, but a callable that
-                    # generates the C code.
-                    args = [a.name for a in line.args]
-                    if line.errtarget:
-                        args.append(line.errtarget)
-                    code = writer(*args)
+                code = line.write()
                 if code:
                     for codeline in code.split('\n'):
                         print >> f, '\t' + codeline
-
             elif line:  # label
                 print >> f, '   %s:' % line
             else:  # empty line
                 print >> f
         print >> f, '}'
 
-    def lloperation_move(self, line):
-        vx, vy = line.args
-        return '%s = %s;' % (vy.name, vx.name)
-
-    def lloperation_goto(self, line):
-        return 'goto %s;' % line.errtarget
-
-    def lloperation_copy(self, line):
-        ls = []
-        assert len(line.args) % 2 == 0
-        half = len(line.args) // 2
-        for i in range(half):
-            vx = line.args[i]
-            vy = line.args[half+i]
-            ls.append('%s = %s;' % (vy.name, vx.name))
-            if vy.type == 'PyObject*':
-                ls.append('Py_INCREF(%s);' % vy.name)
-        return ' '.join(ls)
-
-    def lloperation_incref(self, line):
-        ls = []
-        for a in line.args:
-            if a.type == 'PyObject*':
-                ls.append('Py_INCREF(%s);' % a.name)
-        return ' '.join(ls)
-
-    def lloperation_decref(self, line):
-        ls = []
-        for a in line.args:
-            if a.type == 'PyObject*':
-                ls.append('Py_DECREF(%s);' % a.name)
-        return ' '.join(ls)
-
-    def lloperation_xdecref(self, line):
-        ls = []
-        for a in line.args:
-            if a.type == 'PyObject*':
-                ls.append('Py_XDECREF(%s);' % a.name)
-        return ' '.join(ls)
-
     def gen_cclass(self, llclass):
         f = self.f
         cls = llclass.cdef.cls

Added: pypy/trunk/src/pypy/translator/genc_op.py
==============================================================================
--- (empty file)
+++ pypy/trunk/src/pypy/translator/genc_op.py	Wed Sep  8 15:55:53 2004
@@ -0,0 +1,244 @@
+"""
+Low-level operations for C code generation.
+"""
+
+from pypy.translator.typer import LLOp
+
+# This file defines one class per possible operation.  But there are families
+# of very similar operations (e.g. incref/decref/xdecref).  To make it easy
+# to follow which class is really an operation and which class represents a
+# family, we introduce a simple mecanism: class attributes that are set to
+# PARAMETER are parameters that can be set by calling the class method With().
+PARAMETER = object()   # marker
+
+
+class LoC(LLOp):
+    # base class for LLOps that produce C code.
+
+    def write(self):
+        "Default write method, delegating to writestr()."
+        args = [a.name for a in self.args]
+        if self.can_fail:
+            args.append(self.errtarget)
+        return self.writestr(*args)
+
+    def using(self):
+        return self.args    # all locals and constants needed by write()
+
+    def With(cls, **params):
+        class subcls(cls):
+            pass
+        items = params.items()
+        items.sort()
+        info = [repr(value) for name, value in items]
+        subcls.__name__ = '%s.With(%s)' % (cls.__name__, ', '.join(info))
+        for name, value in items:
+            assert hasattr(cls, name), 'not a parameter: %r' % (name,)
+            setattr(subcls, name, value)
+        # check that all PARAMETERs, at least from this class,
+        # have been given a value
+        for name, value in cls.__dict__.items():
+            if value is PARAMETER:
+                assert name in params, 'missing definition for parameter '+name
+        return subcls
+    With = classmethod(With)
+
+class LoOptimized(LoC):
+    def write(self):
+        raise NotImplementedError, 'should be optimized away'
+
+# ____________________________________________________________
+
+class LoStandardOperation(LoC):
+    "A standard operation is one defined by a macro in genc.h."
+    can_fail = PARAMETER
+    llname   = PARAMETER
+    def writestr(self, *args):
+        return self.llname + '(' + ', '.join(args) + ')'
+
+class LoKnownAnswer(LoOptimized):
+    known_answer = PARAMETER
+    def optimize(self, typer):
+        return self.known_answer
+
+class LoNewList(LoC):
+    can_fail = True
+    def writestr(self, *stuff):
+        content = stuff[:-2]
+        result = stuff[-2]
+        err = stuff[-1]
+        ls = ['if (!(%s = PyList_New(%d))) goto %s;' % (
+            result, len(content), err)]
+        for i in range(len(content)):
+            ls.append('PyList_SET_ITEM(%s, %d, %s); Py_INCREF(%s);' % (
+                result, i, content[i], content[i]))
+        return '\n'.join(ls)
+
+class LoCallFunction(LoC):
+    can_fail = True
+    def writestr(self, func, *stuff):
+        args = stuff[:-2]
+        result = stuff[-2]
+        err = stuff[-1]
+        format = '"' + 'O' * len(args) + '"'
+        args = (func, format) + args
+        return ('if (!(%s = PyObject_CallFunction(%s)))'
+                ' goto %s;' % (result, ', '.join(args), err))
+
+class LoInstantiate(LoC):
+    can_fail = True
+    llclass  = PARAMETER
+    def writestr(self, res, err):
+        return 'INSTANTIATE(%s, %s, %s)' % (
+            self.llclass.name, res, err)
+
+class LoAllocInstance(LoC):
+    can_fail = True
+    llclass  = PARAMETER
+    def writestr(self, res, err):
+        return 'ALLOC_INSTANCE(%s, %s, %s)' % (
+            self.llclass.name, res, err)
+
+class LoConvertTupleItem(LoOptimized):
+    source_r = PARAMETER   # tuple-of-hltypes, one per item of the input tuple
+    target_r = PARAMETER   # tuple-of-hltypes, one per item of the output tuple
+    index    = PARAMETER   # index of the item to convert
+
+    def optimize(self, typer):
+        # replace this complex conversion by the simpler conversion of
+        # only the indexth item
+        llinputs = []
+        pos = 0
+        for r in self.source_r:
+            L = len(r.impl)
+            llinputs.append(self.args[pos:pos+L])
+            pos += L
+        lloutputs = []
+        for r in self.target_r:
+            L = len(r.impl)
+            lloutputs.append(self.args[pos:pos+L])
+            pos += L
+
+        llrepr = []     # answer
+        for i in range(len(self.source_r)):
+            if i == self.index:
+                # convert this item
+                llrepr += typer.convert(self.source_r[i], llinputs[i],
+                                        self.target_r[i], lloutputs[i])
+            else:
+                # don't convert, just pass this item unchanged to the result
+                llrepr += llinputs[i]
+        return llrepr
+
+class LoNewTuple(LoC):
+    can_fail = True
+
+    def writestr(self, *stuff):
+        args   = stuff[:-2]
+        result = stuff[-2]
+        err    = stuff[-1]
+        ls = ['if (!(%s = PyTuple_New(%d))) goto %s;' %
+              (result, len(args), err)]
+        for i, a in zip(range(len(args)), args):
+            ls.append('PyTuple_SET_ITEM(%s, %d, %s); Py_INCREF(%s);' %
+                      (result, i, a, a))
+        return '\n'.join(ls)
+
+class LoConvertChain(LoOptimized):
+    r_from         = PARAMETER
+    r_middle       = PARAMETER
+    r_to           = PARAMETER
+    convert_length = PARAMETER
+
+    def optimize(self, typer):
+        half = len(self.r_from.impl)
+        assert half + len(self.r_to.impl) == len(self.args)
+        input = self.args[:half]
+        output = self.args[half:]
+        middle = typer.convert(self.r_from, input, self.r_middle)
+        return typer.convert(self.r_middle, middle, self.r_to, output)
+
+# ____________________________________________________________
+
+class LoMove(LoC):
+    def writestr(self, x, y):
+        return '%s = %s;' % (y, x)
+
+class LoGoto(LoC):
+    def write(self):
+        return 'goto %s;' % self.errtarget
+
+class LoCopy(LoOptimized):
+    def optimize(self, typer):
+        # the result's llvars is equal to the input's llvars.
+        assert len(self.args) % 2 == 0
+        half = len(self.args) // 2
+        return self.args[:half]
+
+class LoDoSomethingWithRef(LoC):
+    do_what = PARAMETER
+    def write(self):
+        ls = []
+        for a in self.args:
+            if a.type == 'PyObject*':
+                ls.append('%s(%s);' % (self.do_what, a.name))
+        return ' '.join(ls)
+
+LoIncref  = LoDoSomethingWithRef.With(do_what = 'Py_INCREF')
+LoDecref  = LoDoSomethingWithRef.With(do_what = 'Py_DECREF')
+LoXDecref = LoDoSomethingWithRef.With(do_what = 'Py_XDECREF')
+
+class LoComment(LoC):
+    def write(self):
+        s = self.errtarget
+        s = s.replace('/*', '/+')
+        s = s.replace('*/', '+/')
+        return '/* %s */' % s
+
+# ____________________________________________________________
+
+ERROR_RETVAL = {
+    None:        '-1',
+    'int':       '-1',
+    'PyObject*': 'NULL',
+    }
+
+ERROR_CHECK = {
+    None:        '< 0',
+    'int':       '== -1 && PyErr_Occurred()',
+    'PyObject*': '== NULL',
+    }
+
+class LoCallPyFunction(LoC):
+    can_fail  = True
+    llfunc    = PARAMETER
+    hlrettype = PARAMETER
+    def write(self):
+        L = len(self.hlrettype.impl)
+        R = len(self.args) - L
+        args = [a.name for a in self.args[:R]]
+        err = self.errtarget
+        if L == 0:  # no return value
+            return 'if (%s(%s) %s) goto %s;' % (
+                self.llfunc.name, ', '.join(args), ERROR_CHECK[None], err)
+        else:
+            # the return value is the first return LLVar:
+            retvar = self.args[R]
+            # if there are several return LLVars, the extra ones are passed
+            # in by reference as output arguments
+            args += ['&%s' % a.name for a in self.args[R+1:]]
+            return ('if ((%s = %s(%s)) %s) goto %s;' % (
+                retvar.name, self.llfunc.name, ', '.join(args),
+                ERROR_CHECK[retvar.type], err))
+
+class LoReturn(LoC):
+    def write(self):
+        if not self.args:
+            return 'return 0;'
+        ls = []
+        for extra in self.args[1:]:
+            ls.append('*output_%s = %s;' % (extra.name, extra.name))
+        ls.append('return %s;' % self.args[0].name)
+        return '\n'.join(ls)
+
+# ____________________________________________________________

Modified: pypy/trunk/src/pypy/translator/genc_typeset.py
==============================================================================
--- pypy/trunk/src/pypy/translator/genc_typeset.py	(original)
+++ pypy/trunk/src/pypy/translator/genc_typeset.py	Wed Sep  8 15:55:53 2004
@@ -1,30 +1,29 @@
 import re, types, __builtin__
-from pypy.objspace.flow.model import Variable, Constant
+from pypy.objspace.flow.model import Variable, Constant, UndefinedConstant
 from pypy.annotation import model as annmodel
 from pypy.translator.typer import LLConst
+from pypy.translator import genc_op
 
 
 class CRepr:
     "A possible representation of a flow-graph variable as C-level variables."
 
-    def __init__(self, impl, err_check=None, parse_code=None):
+    def __init__(self, impl, parse_code=None, name=None):
         self.impl = impl   # list [(C type, prefix for variable name)]
-        self.err_check = err_check  # condition to check for error return value
         self.parse_code = parse_code  # character(s) for PyArg_ParseTuple()
+        self.name = name or '<C %s>' % ' + '.join(self.impl)
 
     def __repr__(self):
-        if hasattr(self, 'const'):
-            return '<C:= %r>' % (self.const,)
-        else:
-            return '<C: %s>' % ' + '.join(self.impl)
+        return self.name
 
 
 class CTypeSet:
     "A (small) set of C types that typer.LLFunction can manipulate."
 
-    R_VOID   = CRepr([])
-    R_INT    = CRepr(['int'],       err_check='< 0',     parse_code='i')
-    R_OBJECT = CRepr(['PyObject*'], err_check='== NULL', parse_code='O')
+    R_VOID     = CRepr([])
+    R_INT      = CRepr(['int'],       parse_code='i')
+    R_OBJECT   = CRepr(['PyObject*'], parse_code='O')
+    #R_DONTCARE = CRepr([])   # for uninitialized variables
 
     REPR_BY_CODE = {
         'v': R_VOID,
@@ -32,12 +31,23 @@
         'o': R_OBJECT,
         }
 
+    rawoperations = {
+        'goto'   : genc_op.LoGoto,
+        'move'   : genc_op.LoMove,
+        'copy'   : genc_op.LoCopy,
+        'incref' : genc_op.LoIncref,
+        'decref' : genc_op.LoDecref,
+        'xdecref': genc_op.LoXDecref,
+        'comment': genc_op.LoComment,
+        'return' : genc_op.LoReturn,
+        }
+
     def __init__(self, genc, bindings):
         self.genc = genc
         self.bindings = bindings
         self.r_constants = {}
         self.r_tuples = {}
-        self.lloperations = {'convert': {}, 'release': {}}
+        self.lloperations = {'convert': {}}
         self.parse_operation_templates()
 
     # __________ methods required by LLFunction __________
@@ -60,6 +70,8 @@
                 return self.tuple_representation(items_r)
             # fall-back
             return self.R_OBJECT
+        #if isinstance(var, UndefinedConstant):
+        #    return self.R_DONTCARE
         if isinstance(var, Constant):
             return self.constant_representation(var.value)
         raise TypeError, var
@@ -74,17 +86,7 @@
             sig = (self.R_OBJECT,) * len(hltypes)
             if sig in opnewlist:
                 return False
-            def writer(*stuff):
-                content = stuff[:-2]
-                result = stuff[-2]
-                err = stuff[-1]
-                ls = ['if (!(%s = PyList_New(%d))) goto %s;' % (
-                    result, len(content), err)]
-                for i in range(len(content)):
-                    ls.append('PyList_SET_ITEM(%s, %d, %s); Py_INCREF(%s);' % (
-                        result, i, content[i], content[i]))
-                return '\n'.join(ls)
-            opnewlist[sig] = writer, True
+            opnewlist[sig] = genc_op.LoNewList
             return True   # retry
         if opname == 'OP_NEWTUPLE':
             opnewtuple = self.lloperations.setdefault('OP_NEWTUPLE', {})
@@ -92,28 +94,20 @@
             sig = tuple(hltypes[:-1]) + (rt,)
             if sig in opnewtuple:
                 return False
-            opnewtuple[sig] = 'copy', False
+            opnewtuple[sig] = genc_op.LoCopy
+            # Note that we can use LoCopy to virtually build a tuple because
+            # the tuple representation 'rt' is just the collection of all the
+            # representations for the input args.
             return True   # retry
         if opname == 'OP_SIMPLE_CALL' and hltypes:
             opsimplecall = self.lloperations.setdefault('OP_SIMPLE_CALL', {})
             sig = (self.R_OBJECT,) * len(hltypes)
             if sig in opsimplecall:
                 return False
-            def writer(func, *stuff):
-                args = stuff[:-2]
-                result = stuff[-2]
-                err = stuff[-1]
-                format = '"' + 'O' * len(args) + '"'
-                args = (func, format) + args
-                return ('if (!(%s = PyObject_CallFunction(%s)))'
-                        ' goto %s;' % (result, ', '.join(args), err))
-            opsimplecall[sig] = writer, True
+            opsimplecall[sig] = genc_op.LoCallFunction
             return True   # retry
         return False
 
-    def knownanswer(self, llname):
-        return getattr(llname, 'known_answer', None)
-
     # ____________________________________________________________
 
     def constant_representation(self, value):
@@ -127,26 +121,13 @@
                 items_r = [self.constant_representation(x) for x in value]
                 return self.tuple_representation(items_r)
             # a constant doesn't need any C variable to be encoded
-            r = self.r_constants[key] = CRepr([])
+            r = self.r_constants[key] = CRepr([], name='<const %r>' % (value,))
             r.const = value
-            # returning a constant
-            def writer():
-                return 'return 0;'
-            self.lloperations['return'][r,] = writer, False
-            def writer():
-                return 'return -1;'
-            self.lloperations['returnerr'][r,] = writer, False
-            
+
             # but to convert it to something more general like an int or
             # a PyObject* we need to revive its value, which is done by
             # new conversion operations that we define now
-            conv = self.lloperations['convert']
             if isinstance(value, int):
-                # can convert the constant to a C int
-                def writer(z):
-                    return '%s = %d;' % (z, value)
-                conv[r, self.R_INT] = writer, False
-                writer.known_answer = [LLConst(self.R_INT, '%d' % value)]
                 # can convert the constant to a PyObject*
                 if value >= 0:
                     name = 'g_IntObj_%d' % value
@@ -154,6 +135,10 @@
                     name = 'g_IntObj_minus%d' % abs(value)
                 self.can_convert_to_pyobj(r, 'PyInt_FromLong(%d)' % value,
                                           name)
+                # can convert the constant to a C int
+                self.register_conv(r, self.R_INT, genc_op.LoKnownAnswer.With(
+                    known_answer = [LLConst(self.R_INT, '%d' % value)],
+                    ))
             elif isinstance(value, str):
                 # can convert the constant to a PyObject*
                 self.can_convert_to_pyobj(r,
@@ -172,23 +157,10 @@
                     sig.append(self.gethltype(v))
                 hltype = self.gethltype(llfunc.graph.getreturnvar())
                 sig.append(hltype)
-                if len(hltype.impl) == 0:   # no return value
-                    def writer(*stuff):
-                        args = stuff[:-1]
-                        err = stuff[-1]
-                        return 'if (%s(%s) < 0) goto %s;' % (
-                            llfunc.name, ', '.join(args), err)
-                elif len(hltype.impl) == 1:  # one LLVar for the return value
-                    def writer(*stuff):
-                        args = stuff[:-2]
-                        result = stuff[-2]
-                        err = stuff[-1]
-                        return ('if ((%s = %s(%s)) %s) goto %s;' % (
-                            result, llfunc.name, ', '.join(args),
-                            hltype.err_check, err))
-                else:
-                    XXX("to do")
-                ops[tuple(sig)] = writer, True
+                ops[tuple(sig)] = genc_op.LoCallPyFunction.With(
+                    llfunc = llfunc,
+                    hlrettype = hltype,
+                    )
             elif (isinstance(value, types.BuiltinFunctionType) and
                   value is getattr(__builtin__, value.__name__, None)):
                 # a function from __builtin__: can convert to PyObject*
@@ -202,38 +174,33 @@
                 opname = 'CALL_' + value.__name__
                 if opname in self.lloperations:
                     ops = self.lloperations.setdefault('OP_SIMPLE_CALL', {})
-                    for sig, ll in self.lloperations[opname].items():
+                    for sig, llopcls in self.lloperations[opname].items():
                         sig = (r,) + sig
-                        ops[sig] = ll
+                        ops[sig] = llopcls
             elif (isinstance(value, (type, types.ClassType)) and
                   value in self.genc.llclasses):
                 # a user-defined class
                 ops = self.lloperations.setdefault('OP_SIMPLE_CALL', {})
                 # XXX do __init__
                 sig = (r, self.R_OBJECT)
-                def writer(res, err):
-                    return 'INSTANTIATE(%s, %s, %s)' % (
-                        self.genc.llclasses[value].name, res, err)
-                ops[sig] = writer, True
+                ops[sig] = genc_op.LoInstantiate.With(
+                    llclass = self.genc.llclasses[value],
+                    )
                 # OP_ALLOC_INSTANCE used by the constructor function xxx_new()
                 ops = self.lloperations.setdefault('OP_ALLOC_INSTANCE', {})
                 sig = (r, self.R_OBJECT)
-                def writer(res, err):
-                    return 'ALLOC_INSTANCE(%s, %s, %s)' % (
-                        self.genc.llclasses[value].name, res, err)
-                ops[sig] = writer, True
+                ops[sig] = genc_op.LoAllocInstance.With(
+                    llclass = self.genc.llclasses[value],
+                    )
             else:
                 print "// XXX not implemented: constant", key
             return r
 
     def can_convert_to_pyobj(self, r, initexpr, globalname):
-        conv = self.lloperations['convert']
-        def writer(z):
-            return '%s = %s; Py_INCREF(%s);' % (z, globalname, z)
-        conv[r, self.R_OBJECT] = writer, False
-        llconst = LLConst('PyObject*', globalname, initexpr,
-                          to_declare = bool(initexpr))
-        writer.known_answer = [llconst]
+        self.register_conv(r, self.R_OBJECT, genc_op.LoKnownAnswer.With(
+            known_answer = [LLConst('PyObject*', globalname, initexpr,
+                                    to_declare = bool(initexpr))],
+            ))
 
     def tuple_representation(self, items_r):
         # a tuple is implemented by several C variables or fields
@@ -245,50 +212,34 @@
             impl = []
             for r in items_r:
                 impl += r.impl
-            rt = CRepr(impl)
-            if items_r:
-                rt.err_check = items_r[0].err_check
+            name = '<(%s)>' % ', '.join([str(r) for r in items_r])
+            rt = CRepr(impl, name=name)
             self.r_tuples[items_r] = rt
-            # can convert the tuple to a PyTupleObject only if each item can be
+
+            # we can convert any item in the tuple to obtain another tuple
+            # representation.
             conv = self.lloperations['convert']
-            also_using = []
-            for r in items_r:
-                if r == self.R_OBJECT:
-                    continue
-                if (r, self.R_OBJECT) not in conv:
-                    break
-                llname, can_fail = conv[r, self.R_OBJECT]
-                also_using.append(llname)
-            else:
-                def writer(*args):
-                    content = args[:-2]
-                    result = args[-2]
-                    err = args[-1]
-                    ls = ['{',
-                          'PyObject* o;',
-                          'if (!(%s = PyTuple_New(%d))) goto %s;' % (
-                                result, len(items_r), err)]
-                    j = 0
-                    for i in range(len(items_r)):
-                        r = items_r[i]
-                        if r == self.R_OBJECT:
-                            o = content[j]
-                            j = j+1
-                            ls.append('Py_INCREF(%s);' % o)
-                        else:
-                            o = 'o'
-                            llname, can_fail = conv[r, self.R_OBJECT]
-                            k = len(r.impl)
-                            args = content[j:j+k] + (o,)
-                            j = j+k
-                            if can_fail:
-                                args += (err,)
-                            ls.append(llname(*args))
-                        ls.append('PyTuple_SET_ITEM(%s, %d, %s);' %
-                                  (result, i, o))
-                    return '\n'.join(ls).replace('\n', '\n\t') + '\n}'
-                writer.also_using = also_using
-                conv[rt, self.R_OBJECT] = writer, True
+            for i in range(len(items_r)):
+                r = items_r[i]
+                for r_from, r_to in conv.keys():
+                    if r_from == r:
+                        target_r = list(items_r)
+                        target_r[i] = r_to
+                        rt_2 = self.tuple_representation(target_r)
+                        self.register_conv(rt, rt_2,
+                                           genc_op.LoConvertTupleItem.With(
+                            source_r = items_r,
+                            target_r = target_r,
+                            index    = i,
+                            ))
+
+            # a tuple containing only PyObject* can easily be converted to
+            # a PyTupleObject.  (For other kinds of tuple the conversion is
+            # indirect: all items can probably be converted, one by one, to
+            # PyObject*, and the conversions will be chained automatically.)
+            if items_r == (self.R_OBJECT,) * len(items_r):
+                self.register_conv(rt, self.R_OBJECT, genc_op.LoNewTuple)
+
             return rt
 
     def parse_operation_templates(self):
@@ -307,11 +258,50 @@
         can_fail = formalargs.replace(' ','').endswith(',err')
         ops = self.lloperations.setdefault(opname, {})
         assert sig not in ops, llname
-        # the operation's low-level name is a callable that will
-        # produce the correct macro call
-        def writer(*args):
-            return llname + '(' + ', '.join(args) + ')'
-        ops.setdefault(sig, (writer, can_fail))
+        ops.setdefault(sig, genc_op.LoStandardOperation.With(
+            can_fail = can_fail,
+            llname   = llname,
+            ))
+
+    def register_conv(self, r_from, r_to, convopcls):
+        conv = self.lloperations['convert']
+        if r_from == r_to:
+            return
+        prevconvopcls = conv.get((r_from, r_to))
+        if prevconvopcls is not None:
+            if convert_length(prevconvopcls) > convert_length(convopcls):
+                # only replace a conversion with another if the previous one
+                # was a longer chain of conversions
+                del conv[r_from, r_to]
+            else:
+                return
+        #print 'conv: %s\t->\t%s' % (r_from, r_to)
+        convitems = conv.items()   # not iteritems()!
+        conv[r_from, r_to] = convopcls
+        # chain the conversion with any other possible conversion
+        for (r_from_2, r_to_2), convopcls_2 in convitems:
+            if r_to == r_from_2:
+                self.register_conv(r_from, r_to_2, genc_op.LoConvertChain.With(
+                    r_from         = r_from,
+                    r_middle       = r_to,
+                    r_to           = r_to_2,
+                    convert_length = convert_length(convopcls) +
+                                     convert_length(convopcls_2),
+                    ))
+            if r_to_2 == r_from:
+                self.register_conv(r_from_2, r_to, genc_op.LoConvertChain.With(
+                    r_from         = r_from_2,
+                    r_middle       = r_to_2,
+                    r_to           = r_to,
+                    convert_length = convert_length(convopcls_2) +
+                                     convert_length(convopcls),
+                    ))
+
+def convert_length(convopcls):
+    if issubclass(convopcls, genc_op.LoConvertChain):
+        return convopcls.convert_length
+    else:
+        return 1
 
 def c_str(s):
     "Return the C expression for the string 's'."
@@ -331,12 +321,3 @@
                 c = '_%02x' % ord(c)
         l.append(c)
     return ''.join(l)
-
-def consts_used(writer):
-    "Enumerate the global constants that a writer function uses."
-    result = getattr(writer, 'known_answer', [])
-    if hasattr(writer, 'also_using'):
-        result = list(result)
-        for w in writer.also_using:
-            result += consts_used(w)
-    return result

Modified: pypy/trunk/src/pypy/translator/test/test_ctrans.py
==============================================================================
--- pypy/trunk/src/pypy/translator/test/test_ctrans.py	(original)
+++ pypy/trunk/src/pypy/translator/test/test_ctrans.py	Wed Sep  8 15:55:53 2004
@@ -96,7 +96,8 @@
                 if isinstance(spec, tuple):
                     spec = spec[0] # use the first type only for the tests
                 argstypelist.append(spec)
-        t.annotate(argstypelist) 
+        a = t.annotate(argstypelist)
+        a.simplify()
         return t.ccompile()
 
     def test_set_attr(self):

Modified: pypy/trunk/src/pypy/translator/translator.py
==============================================================================
--- pypy/trunk/src/pypy/translator/translator.py	(original)
+++ pypy/trunk/src/pypy/translator/translator.py	Wed Sep  8 15:55:53 2004
@@ -37,7 +37,7 @@
 from pypy.translator.simplify import simplify_graph
 from pypy.translator.genpyrex import GenPyrex
 from pypy.translator.gencl import GenCL
-from pypy.translator.genc import GenC
+from pypy.translator.genc import GenC, uniquemodulename
 from pypy.translator.tool.buildpyxmodule import make_module_from_pyxstring
 from pypy.translator.tool.buildpyxmodule import make_module_from_c
 from pypy.objspace.flow import FlowObjSpace
@@ -220,13 +220,13 @@
         """Returns compiled function, compiled using the C generator.
         """
         from pypy.tool.udir import udir
-        name = self.entrypoint.func_name
+        name = uniquemodulename(self.entrypoint.func_name)
         cfile = udir.join('%s.c' % name)
         f = cfile.open('w')
         GenC(f, self, name)
         f.close()
         mod = make_module_from_c(cfile)
-        return getattr(mod, name)
+        return getattr(mod, self.entrypoint.func_name)
 
     def call(self, *args):
         """Calls underlying Python function."""

Modified: pypy/trunk/src/pypy/translator/typer.py
==============================================================================
--- pypy/trunk/src/pypy/translator/typer.py	(original)
+++ pypy/trunk/src/pypy/translator/typer.py	Wed Sep  8 15:55:53 2004
@@ -3,7 +3,7 @@
 """
 
 from __future__ import generators
-from pypy.objspace.flow.model import Constant, Variable, Block, Link, traverse
+from pypy.objspace.flow.model import Variable, Block, Link, traverse
 from pypy.translator.simplify import remove_direct_loops
 
 
@@ -20,13 +20,21 @@
         self.initexpr = initexpr
         self.to_declare = to_declare
 
-class LLOp:
-    "A low-level operation."
-    def __init__(self, name, args, errtarget=None):
-        self.name = name    # low-level operation name
-        self.args = args    # list of LLVars
+class LLOp(object):
+    "A low-level operation.  Must be subclassed, one subclass per operation."
+    can_fail = False  # boolean attribute: should an errtarget be generated?
+
+    def __init__(self, args, errtarget=None):
+        self.args = args            # list of LLVars
         self.errtarget = errtarget  # label to jump to in case of error
 
+    def optimize(self, typer):
+        """If the operation can be statically optimized, this method can
+        return a list [new-llvars-for-result] and optionally generate
+        replacement operations by calling typer.operation() or
+        typer.convert()."""
+        return None
+
 class TypingError(Exception):
     pass
 
@@ -43,32 +51,32 @@
 #
 #   lloperations = {
 #       'opname': {
-#           (tuple-of-hltypes): (low-level-name, flag-can-fail),
+#           (tuple-of-hltypes): subclass-of-LLOp,
 #           ... },
 #       ...}
 #       This dict contains the known signatures of each space operation.
 #       Special opnames:
-#         'convert' x y : convert some x to y
-#         'caseXXX'   z : fails (i.e. jump to errlabel) if z is not XXX
-#         'return'    z : z is the return value of the function
-#         'returnerr' z : same, but return an error code instead of z's content
+#         'convert' v w : convert some v to w
+#         'caseXXX'   v : fails (i.e. jump to errlabel) if v is not XXX
 #
-#       Low-level-only operation names:
+#   rawoperations = {
+#       'opname': subclass-of-LLOp,
+#       ...}
+#       Low-level-only operations on raw LLVars (as opposed to llopeerations,
+#       which are on flow.model.Variables as found in SpaceOperations):
 #         'goto'          : fails unconditionally (i.e. jump to errlabel)
 #         'move'    x y   : raw copy of the LLVar x to the LLVar y
 #         'copy' x1 x2.. y1 y2...: raw copy x1 to y1, x2 to y2, etc. and incref
 #         'incref'  x y...: raw incref of the LLVars x, y, etc.
 #         'decref'  x y...: raw decref of the LLVars x, y, etc.
 #         'xdecref' x y...: raw xdecref of the LLVars x, y, etc.
+#         'comment'       : comment (text is in errtarget)
+#         'return'  x y...: return the value stored in the LLVars
 #
 #   def typingerror(self, opname, hltypes):
 #       Called when no match is found in lloperations.  This function must
 #       either extend lloperations and return True to retry, or return
 #       False to fail.
-#
-#   def knownanswer(self, llname):
-#       Optionally returns a list of LLVars that give the well-known, constant
-#       answer to the low-level operation name 'llname'; else return None.
 # ____________________________________________________________
 
 
@@ -79,8 +87,8 @@
         self.gethltype    = typeset.gethltype
         self.represent    = typeset.represent
         self.lloperations = typeset.lloperations
+        self.rawoperations= typeset.rawoperations
         self.typingerror  = typeset.typingerror
-        self.knownanswer  = typeset.knownanswer
         self.hltypes = {}
         self.llreprs = {}
 
@@ -123,26 +131,17 @@
         v = self.graph.getreturnvar()
         self.makevar(v)
         llret = self.llreprs[v]
-        if len(llret) == 0:
-            retlltype = None
-        elif len(llret) == 1:
-            retlltype = llret[0].type
-        else:
-            XXX("to do")
-        return llrepr, retlltype
+        return llrepr, llret
 
-    def ll_body(self):
+    def ll_body(self, error_retvals):
         """
         Get the body by flattening and low-level-izing the flow graph.
         Enumerates low-level operations: LLOps with labels inbetween (strings).
         """
         self.blockname = {}
-        v = self.graph.getreturnvar()
-        self.makevar(v)
-        sig = (self.hltypes[v],)
-        llname, can_fail = self.lloperations['returnerr'][sig]
-        assert not can_fail
-        self.release_root = ReleaseNode(None, LLOp(llname, []), None)
+        llreturn = self.rawoperations['return']
+        assert not llreturn.can_fail
+        self.release_root = ReleaseNode(None, llreturn(error_retvals), None)
         allblocks = []
         
         # collect all blocks
@@ -156,7 +155,7 @@
 
         # generate an incref for each input argument
         for v in self.graph.getargs():
-            yield LLOp('incref', self.llreprs[v])
+            yield self.rawoperations['incref'](self.llreprs[v])
 
         # generate the body of each block
         for block in allblocks:
@@ -165,7 +164,7 @@
             yield ''   # empty line
 
         # generate the code to handle errors
-        for op in self.release_root.error_code():
+        for op in self.release_root.error_code(self.rawoperations):
             yield op
 
     def generate_block(self, block):
@@ -198,13 +197,16 @@
         elif hasattr(block, 'exc_type'):
             XXX("to do")
         else:
-            self.operation('return', block.inputargs)
+            llreturn = self.rawoperations['return']
+            assert not llreturn.can_fail
+            llrepr = self.llreprs[block.inputargs[0]]
+            self.blockops.append(llreturn(llrepr))
         return self.blockops
 
     # __________ Type checking and conversion routines __________
 
     def mark_release(self, v):
-        llop = LLOp('decref', self.llreprs[v])
+        llop = self.rawoperations['decref'](self.llreprs[v])
         # make a new node for the release tree
         self.to_release = ReleaseNode(v, llop, self.to_release)
 
@@ -236,7 +238,7 @@
         llsigs = self.lloperations.get(opname, {})
         for sig in variants(tuple(args_t), directions):
             if sig in llsigs:
-                llname, can_fail = llsigs[sig]
+                llopcls = llsigs[sig]
                 break
         else:
             retry = self.typingerror(opname, tuple(args_t))
@@ -250,52 +252,66 @@
                 except RuntimeError:   # infinite recursion
                     pass
             raise TypingError([opname] + args_t)
-        # check for some operations that have an existing well-known answer
-        # that doesn't involve side-effects, so that we can just provide
-        # this answer and not generate any LLOp.
-        if result:
-            if llname == 'copy':
-                # patch self.llreprs and we're done
-                llrepr = []
-                for v in args:
-                    llrepr += self.llreprs[v]
-                self.llreprs[result] = llrepr
-                return
-            llrepr = self.knownanswer(llname)
-            if llrepr is not None:
-                # patch self.llreprs and we're done
-                self.llreprs[result] = llrepr
-                return
         # convert input args to temporary variables
         llargs = []
         for v, v_t, s_t in zip(args, args_t, sig):
             if v_t != s_t:
-                tmp = Variable()
-                self.makevar(tmp, hltype=s_t)
-                self.operation('convert', [v], tmp)
-                v = tmp
-            llargs += self.llreprs[v]
+                llargs += self.convert(v_t, self.llreprs[v], s_t)
+            else:
+                llargs += self.llreprs[v]
         # generate an error label if the operation can fail
-        if can_fail and errlabel is None:
+        if llopcls.can_fail and errlabel is None:
             errlabel = self.to_release.getlabel()
         # case-by-case analysis of the result variable
         if result:
             if args_t[-1] == sig[-1]:
                 # the result has the correct type
-                llargs += self.llreprs[result]
-                self.blockops.append(LLOp(llname, llargs, errlabel))
-                self.mark_release(result)
+                tmp = result
             else:
                 # the result has to be converted
                 tmp = Variable()
                 self.makevar(tmp, hltype=sig[-1])
-                llargs += self.llreprs[tmp]
-                self.blockops.append(LLOp(llname, llargs, errlabel))
+            llargs += self.llreprs[tmp]
+            llop = llopcls(llargs, errlabel)
+            constantllreprs = llop.optimize(self)
+            if constantllreprs is not None:
+                # the result is a constant: patch the llrepr of result,
+                # i.e. replace its LLVars with the given constants which
+                # will be used by the following operations.
+                assert len(constantllreprs) == len(self.llreprs[tmp])
+                diffs = []
+                interesting = False
+                for x, y in zip(constantllreprs, self.llreprs[tmp]):
+                    if x != y:
+                        diffs.append('%s = %s;' % (y.name, x.name))
+                        interesting = interesting or not isinstance(x, LLConst)
+                self.llreprs[tmp] = list(constantllreprs)
+                if interesting:
+                    llcomment = self.rawoperations['comment']
+                    self.blockops.append(llcomment([], '%s: %s' % (
+                        opname, ' '.join(diffs))))
+            else:
+                # common case: emit the LLOp.
                 self.mark_release(tmp)
+                self.blockops.append(llop)
+            if tmp is not result:
                 self.operation('convert', [tmp], result)
         else:
             # no result variable
-            self.blockops.append(LLOp(llname, llargs, errlabel))
+            self.blockops.append(llopcls(llargs, errlabel))
+
+    def convert(self, inputtype, inputrepr, outputtype, outputrepr=None):
+        tmpin = Variable()
+        self.makevar(tmpin, hltype=inputtype)
+        tmpout = Variable()
+        self.makevar(tmpout, hltype=outputtype)
+        self.llreprs[tmpin] = inputrepr
+        if outputrepr is None:
+            outputrepr = self.llreprs[tmpout]
+        else:
+            self.llreprs[tmpout] = outputrepr
+        self.operation('convert', [tmpin], tmpout)
+        return self.llreprs[tmpout]
 
     def goto(self, exit):
         # generate the exit.args -> target.inputargs copying operations
@@ -316,9 +332,10 @@
             # the order of the move operations
             current_refcnt = {}
             needed_refcnt = {}
+            llmove = self.rawoperations['move']
             for v, w in zip(exitargs, exit.target.inputargs):
                 for x, y in zip(self.llreprs[v], self.llreprs[w]):
-                    self.blockops.append(LLOp('move', [x, y]))
+                    self.blockops.append(llmove([x, y]))
                     needed_refcnt.setdefault(x, 0)
                     needed_refcnt[x] += 1
             # list all variables that go out of scope: by default
@@ -330,17 +347,20 @@
             # now adjust all reference counters: first increfs, then decrefs
             # (in case a variable to decref points to the same objects than
             #  another variable to incref).
+            llincref = self.rawoperations['incref']
             for x, needed in needed_refcnt.items():
                 current_refcnt.setdefault(x, 0)
                 while current_refcnt[x] < needed:
-                    self.blockops.append(LLOp('incref', [x]))
+                    self.blockops.append(llincref([x]))
                     current_refcnt[x] += 1
+            lldecref = self.rawoperations['decref']
             for x, needed in needed_refcnt.items():
                 while current_refcnt[x] > needed:
-                    self.blockops.append(LLOp('decref', [x]))
+                    self.blockops.append(lldecref([x]))
                     current_refcnt[x] -= 1
             # finally jump to the target block
-            self.blockops.append(LLOp('goto', [], self.blockname[exit.target]))
+            llgoto = self.rawoperations['goto']
+            self.blockops.append(llgoto([], self.blockname[exit.target]))
         finally:
             self.to_release = to_release_copy
             # after a call to goto() we are back to generating ops for
@@ -387,13 +407,14 @@
             yield self
             self = self.parent
 
-    def error_code(self):
+    def error_code(self, rawoperations):
         N = len(self.accessible_children)
         for i in range(N):
             if i > 0:
-                yield LLOp('goto', [], self.getlabel())
+                llgoto = rawoperations['goto']
+                yield llgoto([], self.getlabel())
             node = self.accessible_children[~i]
-            for op in node.error_code():
+            for op in node.error_code(rawoperations):
                 yield op
         if self.label:
             yield self.label



More information about the Pypy-commit mailing list