Author: Armin Rigo <[email protected]>
Branch: cffi-1.0
Changeset: r1713:0b449baa47ff
Date: 2015-04-15 18:13 +0200
http://bitbucket.org/cffi/cffi/changeset/0b449baa47ff/
Log: Starting working on the "recompiler"
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -1,5 +1,6 @@
import sys, types
from .lock import allocate_lock
+import _cffi1_backend
try:
callable
@@ -27,7 +28,7 @@
return '%s%s' % (line, self.args[0])
-class FFI(object):
+class FFI(_cffi1_backend.FFI):
r'''
The main top-level class that you instantiate once, or once per module.
@@ -45,21 +46,16 @@
C.printf("hello, %s!\n", ffi.new("char[]", "world"))
'''
- def __init__(self, backend=None):
+ def __init__(self):
"""Create an FFI instance. The 'backend' argument is used to
select a non-default backend, mostly for tests.
"""
from . import cparser, model
- if backend is None:
- # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with
- # _cffi_backend.so compiled.
- import _cffi_backend as backend
- from . import __version__
- assert backend.__version__ == __version__, \
- "version mismatch, %s != %s" % (backend.__version__,
__version__)
- # (If you insist you can also try to pass the option
- # 'backend=backend_ctypes.CTypesBackend()', but don't
- # rely on it! It's probably not going to work well.)
+ from . import __version__
+
+ backend = _cffi1_backend
+ assert backend.__version__ == __version__, \
+ "version mismatch, %s != %s" % (backend.__version__, __version__)
self._backend = backend
self._lock = allocate_lock()
@@ -80,15 +76,6 @@
with self._lock:
self.BVoidP = self._get_cached_btype(model.voidp_type)
self.BCharA = self._get_cached_btype(model.char_array_type)
- if isinstance(backend, types.ModuleType):
- # _cffi_backend: attach these constants to the class
- if not hasattr(FFI, 'NULL'):
- FFI.NULL = self.cast(self.BVoidP, 0)
- FFI.CData, FFI.CType = backend._get_types()
- else:
- # ctypes backend: attach these constants to the instance
- self.NULL = self.cast(self.BVoidP, 0)
- self.CData, self.CType = backend._get_types()
def cdef(self, csource, override=False, packed=False):
"""Parse the given C source. This registers all declared functions,
@@ -203,7 +190,7 @@
cdecl = self._typeof(cdecl)
return self._typeoffsetof(cdecl, *fields_or_indexes)[1]
- def new(self, cdecl, init=None):
+ def XXXnew(self, cdecl, init=None):
"""Allocate an instance according to the specified C type and
return a pointer to it. The specified C type must be either a
pointer or an array: ``new('X *')`` allocates an X and returns
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -169,6 +169,9 @@
return global_cache(self, ffi, 'new_function_type',
tuple(args), result, self.ellipsis)
+ def as_raw_function(self):
+ return RawFunctionType(self.args, self.result, self.ellipsis)
+
class PointerType(BaseType):
_attrs_ = ('totype',)
diff --git a/new/cffi1 b/new/cffi1
new file mode 120000
--- /dev/null
+++ b/new/cffi1
@@ -0,0 +1,1 @@
+../cffi
\ No newline at end of file
diff --git a/new/cffi1_module.c b/new/cffi1_module.c
--- a/new/cffi1_module.c
+++ b/new/cffi1_module.c
@@ -28,6 +28,9 @@
if (PyDict_SetItemString(FFI_Type.tp_dict, "CType",
(PyObject *)&CTypeDescr_Type) < 0)
return -1;
+ if (PyDict_SetItemString(FFI_Type.tp_dict, "CData",
+ (PyObject *)&CData_Type) < 0)
+ return -1;
Py_INCREF(&FFI_Type);
if (PyModule_AddObject(m, "FFI", (PyObject *)&FFI_Type) < 0)
diff --git a/new/cffi_opcode.py b/new/cffi_opcode.py
new file mode 100644
--- /dev/null
+++ b/new/cffi_opcode.py
@@ -0,0 +1,53 @@
+
+class CffiOp(object):
+ def __init__(self, op, arg):
+ self.op = op
+ self.arg = arg
+ def as_int(self):
+ return self.op | (self.arg << 8)
+ def __str__(self):
+ classname = CLASS_NAME.get(self.op, self.op)
+ return '(%s %d)' % (classname, self.arg)
+
+OP_PRIMITIVE = 1
+OP_POINTER = 3
+OP_ARRAY = 5
+OP_OPEN_ARRAY = 7
+OP_STRUCT_UNION = 9
+OP_ENUM = 11
+OP_TYPENAME = 13
+OP_FUNCTION = 15
+OP_FUNCTION_END = 17
+OP_NOOP = 19
+OP_BITFIELD = 21
+OP_CPYTHON_BLTN_V = 23 # varargs
+OP_CPYTHON_BLTN_N = 25 # noargs
+OP_CPYTHON_BLTN_O = 27 # O (i.e. a single arg)
+
+PRIM_VOID = 0
+PRIM_BOOL = 1
+PRIM_CHAR = 2
+PRIM_SCHAR = 3
+PRIM_UCHAR = 4
+PRIM_SHORT = 5
+PRIM_USHORT = 6
+PRIM_INT = 7
+PRIM_UINT = 8
+PRIM_LONG = 9
+PRIM_ULONG = 10
+PRIM_LONGLONG = 11
+PRIM_ULONGLONG = 12
+PRIM_FLOAT = 13
+PRIM_DOUBLE = 14
+PRIM_LONGDOUBLE = 15
+
+PRIMITIVE_TO_INDEX = {
+ 'int': PRIM_INT,
+ 'float': PRIM_FLOAT,
+ 'double': PRIM_DOUBLE,
+ }
+
+CLASS_NAME = {}
+for _name, _value in globals().items():
+ if _name.startswith('OP_') and isinstance(_value, int):
+ CLASS_NAME[_value] = _name[3:]
diff --git a/new/lib_obj.c b/new/lib_obj.c
--- a/new/lib_obj.c
+++ b/new/lib_obj.c
@@ -1,22 +1,14 @@
-/* A Lib object is what is returned by any of:
-
- - the "lib" attribute of a C extension module originally created by
- recompile()
-
- - ffi.dlopen()
-
- - ffi.verify()
+/* A Lib object is what is in the "lib" attribute of a C extension
+ module originally created by recompile().
A Lib object is special in the sense that it has a custom
__getattr__ which returns C globals, functions and constants. It
- raises AttributeError for anything else, like '__class__'.
+ raises AttributeError for anything else, even attrs like '__class__'.
A Lib object has got a reference to the _cffi_type_context_s
structure, which is used to create lazily the objects returned by
- __getattr__. For a dlopen()ed Lib object, all the 'address' fields
- in _cffi_global_s are NULL, and instead dlsym() is used lazily on
- the l_dl_lib.
+ __getattr__.
*/
struct CPyExtFunc_s {
@@ -29,17 +21,13 @@
PyObject_HEAD
const struct _cffi_type_context_s *l_ctx; /* ctx object */
PyObject *l_dict; /* content, built lazily */
- void *l_dl_lib; /* the result of 'dlopen()', or NULL */
PyObject *l_libname; /* some string that gives the name of the lib
*/
};
#define LibObject_Check(ob) ((Py_TYPE(ob) == &Lib_Type))
-static int lib_close(LibObject *lib); /* forward */
-
static void lib_dealloc(LibObject *lib)
{
- (void)lib_close(lib);
Py_DECREF(lib->l_dict);
Py_DECREF(lib->l_libname);
PyObject_Del(lib);
@@ -228,33 +216,6 @@
offsetof(LibObject, l_dict), /* tp_dictoffset */
};
-
-static void lib_dlerror(LibObject *lib)
-{
- char *error = dlerror();
- if (error == NULL)
- error = "(no error reported)";
- PyErr_Format(PyExc_OSError, "%s: %s", PyText_AS_UTF8(lib->l_libname),
- error);
-}
-
-static int lib_close(LibObject *lib)
-{
- void *dll;
- lib->l_ctx = NULL;
- PyDict_Clear(lib->l_dict);
-
- dll = lib->l_dl_lib;
- if (dll != NULL) {
- lib->l_dl_lib = NULL;
- if (dlclose(dll) != 0) {
- lib_dlerror(lib);
- return -1;
- }
- }
- return 0;
-}
-
static LibObject *lib_internal_new(const struct _cffi_type_context_s *ctx,
char *module_name)
{
@@ -275,7 +236,6 @@
lib->l_ctx = ctx;
lib->l_dict = dict;
- lib->l_dl_lib = NULL;
lib->l_libname = libname;
return lib;
}
diff --git a/new/recompiler.py b/new/recompiler.py
new file mode 100644
--- /dev/null
+++ b/new/recompiler.py
@@ -0,0 +1,132 @@
+import os
+from cffi1 import ffiplatform, model
+from cffi_opcode import *
+
+
+class Recompiler:
+
+ def __init__(self, ffi):
+ self.ffi = ffi
+
+ def collect_type_table(self):
+ self._typesdict = {}
+ self._generate('collecttype')
+ #
+ all_decls = sorted(self._typesdict, key=str)
+ #
+ # prepare all FUNCTION bytecode sequences first
+ self.cffi_types = []
+ for tp in all_decls:
+ if tp.is_raw_function:
+ assert self._typesdict[tp] is None
+ self._typesdict[tp] = len(self.cffi_types)
+ self.cffi_types.append(tp) # placeholder
+ for tp1 in tp.args:
+ assert isinstance(tp1, (model.VoidType,
+ model.PrimitiveType,
+ model.PointerType,
+ model.StructOrUnionOrEnum,
+ model.FunctionPtrType))
+ if self._typesdict[tp1] is None:
+ self._typesdict[tp1] = len(self.cffi_types)
+ self.cffi_types.append(tp1) # placeholder
+ self.cffi_types.append('END') # placeholder
+ #
+ # prepare all OTHER bytecode sequences
+ for tp in all_decls:
+ if not tp.is_raw_function and self._typesdict[tp] is None:
+ self._typesdict[tp] = len(self.cffi_types)
+ self.cffi_types.append(tp) # placeholder
+ if tp.is_array_type and tp.length is not None:
+ self.cffi_types.append('LEN') # placeholder
+ assert None not in self._typesdict.values()
+ #
+ # emit all bytecode sequences now
+ for tp in all_decls:
+ method = getattr(self, '_emit_bytecode_' + tp.__class__.__name__)
+ method(tp, self._typesdict[tp])
+ #
+ # consistency check
+ for op in self.cffi_types:
+ assert isinstance(op, CffiOp)
+
+ def _do_collect_type(self, tp):
+ if not isinstance(tp, model.BaseTypeByIdentity):
+ if isinstance(tp, tuple):
+ for x in tp:
+ self._do_collect_type(x)
+ return
+ if tp not in self._typesdict:
+ self._typesdict[tp] = None
+ if isinstance(tp, model.FunctionPtrType):
+ self._do_collect_type(tp.as_raw_function())
+ else:
+ for _, x in tp._get_items():
+ self._do_collect_type(x)
+
+ def _get_declarations(self):
+ return sorted(self.ffi._parser._declarations.items())
+
+ def _generate(self, step_name):
+ for name, tp in self._get_declarations():
+ kind, realname = name.split(' ', 1)
+ try:
+ method = getattr(self, '_generate_cpy_%s_%s' % (kind,
+ step_name))
+ except AttributeError:
+ raise ffiplatform.VerificationError(
+ "not implemented in verify(): %r" % name)
+ try:
+ method(tp, realname)
+ except Exception as e:
+ model.attach_exception_info(e, name)
+ raise
+
+ def _generate_cpy_function_collecttype(self, tp, name):
+ self._do_collect_type(tp.as_raw_function())
+
+ def _emit_bytecode_PrimitiveType(self, tp, index):
+ prim_index = PRIMITIVE_TO_INDEX[tp.name]
+ self.cffi_types[index] = CffiOp(OP_PRIMITIVE, prim_index)
+
+ def _emit_bytecode_RawFunctionType(self, tp, index):
+ self.cffi_types[index] = CffiOp(OP_FUNCTION,
self._typesdict[tp.result])
+ index += 1
+ for tp1 in tp.args:
+ realindex = self._typesdict[tp1]
+ if index != realindex:
+ if isinstance(tp1, model.PrimitiveType):
+ self._emit_bytecode_PrimitiveType(tp1, index)
+ else:
+ self.cffi_types[index] = CffiOp(OP_NOOP, realindex)
+ index += 1
+ self.cffi_types[index] = CffiOp(OP_FUNCTION_END, tp.ellipsis)
+
+ def _emit_bytecode_PointerType(self, tp, index):
+ self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[tp.totype])
+
+ # ----------
+
+ def _prnt(self, what=''):
+ self._f.write(what + '\n')
+
+ def write_source_to_f(self, f, preamble):
+ self._f = f
+ prnt = self._prnt
+ # first copy some standard set of lines that are mostly '#define'
+ filename = os.path.join(os.path.dirname(__file__), '_cffi_include.h')
+ with open(filename, 'r') as g:
+ prnt(g.read())
+ prnt('/************************************************************/')
+ prnt()
+ # then paste the C source given by the user, verbatim.
+ prnt(preamble)
+ prnt()
+ #...
+
+
+def make_c_source(ffi, target_c_file, preamble):
+ recompiler = Recompiler(ffi)
+ recompiler.collect_type_table()
+ with open(target_c_file, 'w') as f:
+ recompiler.write_source_to_f(f, preamble)
diff --git a/new/test_dlopen.py b/new/test_dlopen.py
new file mode 100644
--- /dev/null
+++ b/new/test_dlopen.py
@@ -0,0 +1,10 @@
+from cffi1 import FFI
+import math
+
+
+def test_math_sin():
+ ffi = FFI()
+ ffi.cdef("double sin(double);")
+ m = ffi.dlopen('m')
+ x = m.sin(1.23)
+ assert x == math.sin(1.23)
diff --git a/new/test_recompiler.py b/new/test_recompiler.py
new file mode 100644
--- /dev/null
+++ b/new/test_recompiler.py
@@ -0,0 +1,42 @@
+from recompiler import Recompiler, make_c_source
+from cffi1 import FFI
+from udir import udir
+
+
+def check_type_table(input, expected_output):
+ ffi = FFI()
+ ffi.cdef(input)
+ recompiler = Recompiler(ffi)
+ recompiler.collect_type_table()
+ assert ''.join(map(str, recompiler.cffi_types)) == expected_output
+
+def test_type_table_func():
+ check_type_table("double sin(double);",
+ "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)")
+ check_type_table("float sin(double);",
+ "(FUNCTION 3)(PRIMITIVE 14)(FUNCTION_END 0)(PRIMITIVE
13)")
+ check_type_table("float sin(void);",
+ "(FUNCTION 2)(FUNCTION_END 0)(PRIMITIVE 13)")
+ check_type_table("double sin(float); double cos(float);",
+ "(FUNCTION 3)(PRIMITIVE 13)(FUNCTION_END 0)(PRIMITIVE
14)")
+ check_type_table("double sin(float); double cos(double);",
+ "(FUNCTION 1)(PRIMITIVE 14)(FUNCTION_END 0)" # cos
+ "(FUNCTION 1)(PRIMITIVE 13)(FUNCTION_END 0)") # sin
+ check_type_table("float sin(double); float cos(float);",
+ "(FUNCTION 4)(PRIMITIVE 14)(FUNCTION_END 0)" # sin
+ "(FUNCTION 4)(PRIMITIVE 13)(FUNCTION_END 0)") # cos
+
+def test_use_noop_for_repeated_args():
+ check_type_table("double sin(double *, double *);",
+ "(FUNCTION 4)(POINTER 4)(NOOP 1)(FUNCTION_END 0)"
+ "(PRIMITIVE 14)")
+
+def test_dont_use_noop_for_primitives():
+ check_type_table("double sin(double, double);",
+ "(FUNCTION 1)(PRIMITIVE 14)(PRIMITIVE 14)(FUNCTION_END
0)")
+
+
+def test_math_sin():
+ ffi = FFI()
+ ffi.cdef("double sin(double);")
+ make_c_source(ffi, str(udir.join('math_sin.c')), '#include <math.h>')
diff --git a/testing/udir.py b/new/udir.py
copy from testing/udir.py
copy to new/udir.py
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit