[cig-commits] r6224 - cs/babel/trunk/spike/Spike/Compiler

leif at geodynamics.org leif at geodynamics.org
Sun Mar 11 09:17:08 PDT 2007


Author: leif
Date: 2007-03-11 09:17:07 -0700 (Sun, 11 Mar 2007)
New Revision: 6224

Modified:
   cs/babel/trunk/spike/Spike/Compiler/ExprNodes.py
   cs/babel/trunk/spike/Spike/Compiler/Nodes.py
   cs/babel/trunk/spike/Spike/Compiler/Parsing.py
   cs/babel/trunk/spike/Spike/Compiler/SpikeTypes.py
   cs/babel/trunk/spike/Spike/Compiler/Symtab.py
Log:
Implemented a hack to address the chief limitation of Pyrex: the
inability to parse C include files.

In Pyrex, to use stuff from "spam.h", one writes:

    cdef extern from "spam.h":
        int spam_counter
        void order_spam(int tons)

>From the documentation: "It's important to understand that Pyrex does
not itself read the C header file, so you still need to provide Pyrex
versions of any declarations from it that you use."

If you only need a handful of declarations, this isn't so bad.  But if
you are wrapping an entire library, it quickly becomes tedious.  (Matt
told me that someone created a Python interface to PETSc by
*generating* Pyrex code from the C header files.  Yuck!)

Spike will be able to parse C header files soon.  But in the interim,
here is an interesting and ugly hack.  Even after the C parser is
fully functional, this hack may prove useful for external code that
Spike is unwilling or unable to parse (namely C++, although Elsa looks
promising).  In any case, I was simply curious whether this hack would
work.

In a .pyx file, one can now write:

    cimport "mpi.h" as cmpi

One can then immediately start using names from 'cmpi':

    cdef cmpi.MPI_Comm comm
    cdef int error, rank
    error = cmpi.MPI_Comm_rank(comm, &rank)

Spike treats all names originating from "cmpi" as having "unknown"
type.  This means that any errors (including typos) are passed through
to the underlying C compiler.  (Since Spike emits #line directives,
the errors will sometimes point back to the original .pyx file.)

Since Spike doesn't know the type of anything from an include file,
casts to and from Python types will not work.  (Currently, these
result in generated code which doesn't compile or link, but in the
future there could be an error message.)  In simple cases, one can
work-around this with the use of a temporary:

    cdef int temp
    temp = ccode.xyz
    return temp

In Pyrex code, the "." operator is used for both "." and "->".
Therefore, for an unknown type, "." is ambiguous.  So, Spike assumes
everything is a struct: "x.y" always generates "x.y" for an unknown
'x'.  But, one can declare a pointer to an unknown type, and all is
well:

    cdef unknown.X *x
    x.field = 42    # generates "x->field = 42"

This hack hasn't been tested thoroughly, but it is good enough for
Pythia's "_mpi.pyx".


Modified: cs/babel/trunk/spike/Spike/Compiler/ExprNodes.py
===================================================================
--- cs/babel/trunk/spike/Spike/Compiler/ExprNodes.py	2007-03-11 05:27:50 UTC (rev 6223)
+++ cs/babel/trunk/spike/Spike/Compiler/ExprNodes.py	2007-03-11 16:17:07 UTC (rev 6224)
@@ -762,7 +762,8 @@
     def check_identifier_kind(self):
         entry = self.entry
         if not (entry.is_const or entry.is_variable 
-            or entry.is_builtin or entry.is_cfunction or entry.is_spikefunction):
+            or entry.is_builtin or entry.is_cfunction or entry.is_spikefunction
+            or entry.is_unknown):
                 if self.entry.as_variable:
                     self.entry = self.entry.as_variable
                 else:
@@ -1288,6 +1289,9 @@
     def analyse_c_function_call(self, env):
         func_type = self.function_type()
         # Check function type
+        if func_type.is_unknown:
+            self.type = func_type
+            return
         if not func_type.is_cfunction:
             if not func_type.is_error:
                 error(self.pos, "Calling non-function type '%s'" %
@@ -1331,11 +1335,19 @@
         return self.c_call_code()
     
     def c_call_code(self):
+        if self.args is None:
+            return "<error>"
+        arg_list_code = []
         func_type = self.function_type()
-        if self.args is None or not func_type.is_cfunction:
+        if func_type.is_unknown:
+            for actual_arg in self.args:
+                arg_list_code.append(actual_arg.result_code)
+            result = "%s(%s)" % (self.function.result_code,
+                join(arg_list_code, ","))
+            return result
+        if not func_type.is_cfunction:
             return "<error>"
         formal_args = func_type.args
-        arg_list_code = []
         for (formal_arg, actual_arg) in \
             zip(formal_args, self.args):
                 arg_code = actual_arg.result_as(formal_arg.type)
@@ -1512,7 +1524,8 @@
             entry = module_scope.lookup_here(self.attribute)
             if entry and (
                 entry.is_cglobal or entry.is_cfunction
-                or entry.is_type or entry.is_const):
+                or entry.is_type or entry.is_const
+                or entry.is_unknown):
                     self.mutate_into_name_node(env, entry, target)
                     return 1
         return 0
@@ -1628,7 +1641,7 @@
             if Options.intern_names:
                 self.interned_attr_cname = env.intern(self.attribute)
         else:
-            if not obj_type.is_error:
+            if not (obj_type.is_error or obj_type.is_unknown):
                 error(self.pos, 
                     "Object of type '%s' has no attribute '%s'" %
                     (obj_type, self.attribute))
@@ -2679,6 +2692,8 @@
         type2 = operand2.type
         if type1.is_error or type2.is_error:
             return 1
+        if type1.is_unknown or type2.is_unknown:
+            return 1
         if type1.is_pyobject: # type2 will be, too
             return 1
         elif type1.is_ptr:

Modified: cs/babel/trunk/spike/Spike/Compiler/Nodes.py
===================================================================
--- cs/babel/trunk/spike/Spike/Compiler/Nodes.py	2007-03-11 05:27:50 UTC (rev 6223)
+++ cs/babel/trunk/spike/Spike/Compiler/Nodes.py	2007-03-11 16:17:07 UTC (rev 6224)
@@ -1546,6 +1546,8 @@
                 entry = scope.find(self.name, self.pos)
                 if entry and entry.is_type:
                     type = entry.type
+                elif entry and entry.is_unknown:
+                    type = entry.type
                 else:
                     error(self.pos, "'%s' is not a type identifier" % self.name)
         if type:
@@ -3684,6 +3686,25 @@
         pass
     
 
+class CImportIncludeStatNode(StatNode):
+    #  'cimport "header.h" as name' statement
+    #
+    #  include_file_name  string  Qualified name of module being imported
+    #  as_name            string  Name specified in "as" clause
+    
+    def analyse_declarations(self, env):
+        module_scope = env.find_submodule(self.as_name)
+        entry = env.declare_module(self.as_name, module_scope, self.pos)
+        entry.as_module.is_entry_into_the_unknown = 1
+        entry.as_module.add_include_file(self.include_file_name)
+
+    def analyse_expressions(self, env):
+        pass
+    
+    def generate_execution_code(self, code):
+        pass
+    
+
 class FromCImportStatNode(StatNode):
     #  from ... cimport statement
     #

Modified: cs/babel/trunk/spike/Spike/Compiler/Parsing.py
===================================================================
--- cs/babel/trunk/spike/Spike/Compiler/Parsing.py	2007-03-11 05:27:50 UTC (rev 6223)
+++ cs/babel/trunk/spike/Spike/Compiler/Parsing.py	2007-03-11 16:17:07 UTC (rev 6224)
@@ -785,6 +785,18 @@
     pos = s.position()
     kind = s.sy
     s.next()
+    if kind == 'cimport':
+        literal = p_opt_string_literal(s)
+        if literal:
+            _, include_file_name = literal
+            as_name = p_as_name(s)
+            if as_name is None:
+                s.error("Expected 'as'")
+            stat = Nodes.CImportIncludeStatNode(
+                pos, 
+                include_file_name = include_file_name,
+                as_name = as_name)
+            return Nodes.StatListNode(pos, stats = [stat])
     items = [p_dotted_name(s, as_allowed = 1)]
     while s.sy == ',':
         s.next()

Modified: cs/babel/trunk/spike/Spike/Compiler/SpikeTypes.py
===================================================================
--- cs/babel/trunk/spike/Spike/Compiler/SpikeTypes.py	2007-03-11 05:27:50 UTC (rev 6223)
+++ cs/babel/trunk/spike/Spike/Compiler/SpikeTypes.py	2007-03-11 16:17:07 UTC (rev 6224)
@@ -71,6 +71,7 @@
     is_string = 0
     is_returncode = 0
     is_error = 0
+    is_unknown = 0
     has_attributes = 0
     default_value = ""
     parsetuple_format = ""
@@ -260,7 +261,12 @@
     to_py_function = None
     from_py_function = None
 
+    def assignable_from(self, src_type):
+        if src_type.is_unknown:
+            return 1
+        return SpikeType.assignable_from(self, src_type)
 
+
 class CSimpleType(CType):
     #
     #  Base class for all unstructured C types.
@@ -283,6 +289,24 @@
         return 0
 
 
+class CUnknownType(CSimpleType):
+    is_unknown = 1
+
+    def __init__(self, name):
+        self.name = name
+    
+    def __repr__(self):
+        return "<CUnknownType>"
+    
+    def declaration_code(self, entity_code, 
+            for_display = 0, dll_linkage = None, pyrex = 0):
+        base = public_decl(self.name, dll_linkage)
+        return "%s %s" % (base, entity_code)
+
+    def assignable_from(self, src_type):
+        return isinstance(src_type, CType)
+
+
 class CNumericType(CType):
     #
     #   Base class for all C numeric types.

Modified: cs/babel/trunk/spike/Spike/Compiler/Symtab.py
===================================================================
--- cs/babel/trunk/spike/Spike/Compiler/Symtab.py	2007-03-11 05:27:50 UTC (rev 6223)
+++ cs/babel/trunk/spike/Spike/Compiler/Symtab.py	2007-03-11 16:17:07 UTC (rev 6224)
@@ -9,7 +9,8 @@
 from SpikeTypes import c_int_type, \
     py_object_type, c_char_array_type, \
     spike_function_type, \
-    CEnumType, CStructOrUnionType, PyExtensionType
+    CEnumType, CStructOrUnionType, PyExtensionType, \
+    CUnknownType
 from TypeSlots import \
     pyfunction_signature, pymethod_signature, \
     get_special_method_signature, get_property_accessor_signature
@@ -63,6 +64,7 @@
     # interned_cname   string     C name of interned name string
     # pystring_cname   string     C name of Python version of string literal
     # is_interned      boolean    For string const entries, value is interned
+    # is_unknown       boolean    Is from an included C header file
 
     borrowed = 0
     init = ""
@@ -94,6 +96,7 @@
     interned_cname = None
     pystring_cname = None
     is_interned = 0
+    is_unknown = 0
     
     def __init__(self, name, cname, type, pos = None, init = None):
         self.name = name
@@ -133,6 +136,7 @@
     is_c_class_scope = 0
     scope_prefix = ""
     in_cinclude = 0
+    is_entry_into_the_unknown = 0
     
     def __init__(self, name, outer_scope, parent_scope):
         # The outer_scope is the next scope in the lookup chain.
@@ -210,6 +214,11 @@
     
     def qualify_name(self, name):
         return "%s.%s" % (self.qualified_name, name)
+
+    def declare_unknown(self, name):
+        entry = self.declare(name, name, CUnknownType(name), ('<unknown>', 0, 0))
+        entry.is_unknown = 1
+        return entry
     
     def declare_const(self, name, type, value, pos, cname = None):
         # Add an entry for a named constant.
@@ -351,7 +360,10 @@
 
     def lookup_here(self, name):
         # Look up in this scope only, return None if not found.
-        return self.entries.get(name, None)
+        entry = self.entries.get(name, None)
+        if entry is None and self.is_entry_into_the_unknown:
+            entry = self.declare_unknown(name)
+        return entry
         
     def lookup_target(self, name):
         # Look up name in this scope only. Declare as Python
@@ -490,6 +502,7 @@
     # intern_map           {string : string}  Mapping from Python names to interned strs
     # interned_names       [string]           Interned names pending generation of declarations
     # all_pystring_entries [Entry]            Python string consts from all scopes
+    # is_entry_into_the_unknown boolean       Contains any name asked for
 
     def __init__(self, name, parent_module, context):
         self.parent_module = parent_module
@@ -514,6 +527,7 @@
         self.intern_map = {}
         self.interned_names = []
         self.all_pystring_entries = []
+        self.is_entry_into_the_unknown = 0
     
     def qualifying_scope(self):
         return self.parent_module



More information about the cig-commits mailing list