Commit cf455579 authored by Armin Rigo's avatar Armin Rigo
Browse files

Refactor the way global variables are accessed. Now, every access to

"lib.var" calls a generated C function to get "&var".  This fixes issue
#212 and also make it work in more cases, like __thread variables.
parent 0f549bccae62
typedef void *(*gs_fetch_addr_fn)(void);
typedef struct {
PyObject_HEAD
CTypeDescrObject *gs_type;
char *gs_data;
gs_fetch_addr_fn gs_fetch_addr;
} GlobSupportObject;
......@@ -38,7 +41,8 @@ static PyTypeObject GlobSupport_Type = {
#define GlobSupport_Check(ob) (Py_TYPE(ob) == &GlobSupport_Type)
static PyObject *make_global_var(CTypeDescrObject *type, char *addr)
static PyObject *make_global_var(CTypeDescrObject *type, char *addr,
gs_fetch_addr_fn fetch_addr)
{
GlobSupportObject *gs = PyObject_New(GlobSupportObject, &GlobSupport_Type);
if (gs == NULL)
......@@ -47,26 +51,47 @@ static PyObject *make_global_var(CTypeDescrObject *type, char *addr)
Py_INCREF(type);
gs->gs_type = type;
gs->gs_data = addr;
gs->gs_fetch_addr = fetch_addr;
return (PyObject *)gs;
}
static void *fetch_global_var_addr(GlobSupportObject *gs)
{
void *data;
if (gs->gs_data != NULL) {
data = gs->gs_data;
}
else {
Py_BEGIN_ALLOW_THREADS
restore_errno();
data = gs->gs_fetch_addr();
save_errno();
Py_END_ALLOW_THREADS
}
return data;
}
static PyObject *read_global_var(GlobSupportObject *gs)
{
return convert_to_object(gs->gs_data, gs->gs_type);
void *data = fetch_global_var_addr(gs);
return convert_to_object(data, gs->gs_type);
}
static int write_global_var(GlobSupportObject *gs, PyObject *obj)
{
return convert_from_object(gs->gs_data, gs->gs_type, obj);
void *data = fetch_global_var_addr(gs);
return convert_from_object(data, gs->gs_type, obj);
}
static PyObject *cg_addressof_global_var(GlobSupportObject *gs)
{
void *data;
PyObject *x, *ptrtype = new_pointer_type(gs->gs_type);
if (ptrtype == NULL)
return NULL;
x = new_simple_cdata(gs->gs_data, (CTypeDescrObject *)ptrtype);
data = fetch_global_var_addr(gs);
x = new_simple_cdata(data, (CTypeDescrObject *)ptrtype);
Py_DECREF(ptrtype);
return x;
}
......@@ -267,7 +267,8 @@ static PyObject *lib_build_and_cache_attr(LibObject *lib, PyObject *name,
return NULL;
if (ct->ct_size <= 0) {
PyErr_SetString(PyExc_SystemError, "constant has no known size");
PyErr_Format(FFIError, "constant '%s' is of type '%s', "
"whose size is not known", s, ct->ct_name);
return NULL;
}
if (g->address == NULL) {
......@@ -299,7 +300,10 @@ static PyObject *lib_build_and_cache_attr(LibObject *lib, PyObject *name,
case _CFFI_OP_GLOBAL_VAR:
{
/* global variable of the exact type specified here */
/* global variable of the exact type specified here
(nowadays, only used by the ABI mode or backward
compatibility; see _CFFI_OP_GLOBAL_VAR_F for the API mode)
*/
Py_ssize_t g_size = (Py_ssize_t)g->size_or_direct_fn;
ct = realize_c_type(types_builder, types_builder->ctx.types,
_CFFI_GETARG(g->type_op));
......@@ -320,12 +324,21 @@ static PyObject *lib_build_and_cache_attr(LibObject *lib, PyObject *name,
if (address == NULL)
return NULL;
}
x = make_global_var(ct, address);
x = make_global_var(ct, address, NULL);
}
Py_DECREF(ct);
break;
}
case _CFFI_OP_GLOBAL_VAR_F:
ct = realize_c_type(types_builder, types_builder->ctx.types,
_CFFI_GETARG(g->type_op));
if (ct == NULL)
return NULL;
x = make_global_var(ct, NULL, (gs_fetch_addr_fn)g->address);
Py_DECREF(ct);
break;
case _CFFI_OP_DLOPEN_FUNC:
{
/* For dlopen(): the function of the given 'name'. We use
......@@ -378,22 +391,25 @@ static PyObject *lib_build_and_cache_attr(LibObject *lib, PyObject *name,
} \
} while (0)
static PyObject *_lib_dir1(LibObject *lib, int ignore_type)
static PyObject *_lib_dir1(LibObject *lib, int ignore_global_vars)
{
const struct _cffi_global_s *g = lib->l_types_builder->ctx.globals;
int i, count = 0, total = lib->l_types_builder->ctx.num_globals;
PyObject *lst = PyList_New(total);
PyObject *s, *lst = PyList_New(total);
if (lst == NULL)
return NULL;
for (i = 0; i < total; i++) {
if (_CFFI_GETOP(g[i].type_op) != ignore_type) {
PyObject *s = PyText_FromString(g[i].name);
if (s == NULL)
goto error;
PyList_SET_ITEM(lst, count, s);
count++;
if (ignore_global_vars) {
int op = _CFFI_GETOP(g[i].type_op);
if (op == _CFFI_OP_GLOBAL_VAR || op == _CFFI_OP_GLOBAL_VAR_F)
continue;
}
s = PyText_FromString(g[i].name);
if (s == NULL)
goto error;
PyList_SET_ITEM(lst, count, s);
count++;
}
if (PyList_SetSlice(lst, count, total, NULL) < 0)
goto error;
......@@ -445,7 +461,7 @@ static PyObject *lib_getattr(LibObject *lib, PyObject *name)
missing:
if (strcmp(PyText_AsUTF8(name), "__all__") == 0) {
PyErr_Clear();
return _lib_dir1(lib, _CFFI_OP_GLOBAL_VAR);
return _lib_dir1(lib, 1);
}
if (strcmp(PyText_AsUTF8(name), "__dict__") == 0) {
PyErr_Clear();
......@@ -481,7 +497,7 @@ static int lib_setattr(LibObject *lib, PyObject *name, PyObject *val)
static PyObject *lib_dir(PyObject *self, PyObject *noarg)
{
return _lib_dir1((LibObject *)self, -1);
return _lib_dir1((LibObject *)self, 0);
}
static PyMethodDef lib_methods[] = {
......
......@@ -53,6 +53,7 @@ OP_CONSTANT_INT = 31
OP_GLOBAL_VAR = 33
OP_DLOPEN_FUNC = 35
OP_DLOPEN_CONST = 37
OP_GLOBAL_VAR_F = 39
PRIM_VOID = 0
PRIM_BOOL = 1
......
......@@ -35,9 +35,6 @@ class BaseTypeByIdentity(object):
def is_integer_type(self):
return False
def sizeof_enabled(self):
return False
def get_cached_btype(self, ffi, finishlist, can_delay=False):
try:
BType = ffi._cached_btypes[self]
......@@ -80,8 +77,7 @@ void_type = VoidType()
class BasePrimitiveType(BaseType):
def sizeof_enabled(self):
return True
pass
class PrimitiveType(BasePrimitiveType):
......@@ -205,9 +201,6 @@ class RawFunctionType(BaseFunctionType):
class FunctionPtrType(BaseFunctionType):
_base_pattern = '(*&)(%s)'
def sizeof_enabled(self):
return True
def build_backend_type(self, ffi, finishlist):
result = self.result.get_cached_btype(ffi, finishlist)
args = []
......@@ -233,9 +226,6 @@ class PointerType(BaseType):
extra = self._base_pattern
self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra)
def sizeof_enabled(self):
return True
def build_backend_type(self, ffi, finishlist):
BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True)
return global_cache(self, ffi, 'new_pointer_type', BItem)
......@@ -276,9 +266,6 @@ class ArrayType(BaseType):
self.c_name_with_marker = (
self.item.c_name_with_marker.replace('&', brackets))
def sizeof_enabled(self):
return self.item.sizeof_enabled() and self.length is not None
def resolve_length(self, newlength):
return ArrayType(self.item, newlength)
......@@ -433,9 +420,6 @@ class StructOrUnion(StructOrUnionOrEnum):
from . import ffiplatform
raise ffiplatform.VerificationMissing(self._get_c_name())
def sizeof_enabled(self):
return self.fldtypes is not None
def build_backend_type(self, ffi, finishlist):
self.check_not_partial()
finishlist.append(self)
......@@ -464,9 +448,6 @@ class EnumType(StructOrUnionOrEnum):
self.baseinttype = baseinttype
self.build_c_name_with_marker()
def sizeof_enabled(self):
return True # not strictly true, but external enums are obscure
def force_the_name(self, forcename):
StructOrUnionOrEnum.force_the_name(self, forcename)
if self.forcename is None:
......
......@@ -26,6 +26,7 @@ typedef void *_cffi_opcode_t;
#define _CFFI_OP_GLOBAL_VAR 33
#define _CFFI_OP_DLOPEN_FUNC 35
#define _CFFI_OP_DLOPEN_CONST 37
#define _CFFI_OP_GLOBAL_VAR_F 39
#define _CFFI_PRIM_VOID 0
#define _CFFI_PRIM_BOOL 1
......
......@@ -981,10 +981,6 @@ class Recompiler:
if not self.target_is_python and tp.is_integer_type():
type_op = CffiOp(OP_CONSTANT_INT, -1)
else:
if not tp.sizeof_enabled():
raise ffiplatform.VerificationError(
"constant '%s' is of type '%s', whose size is not known"
% (name, tp._get_c_name()))
if self.target_is_python:
const_kind = OP_DLOPEN_CONST
else:
......@@ -1069,18 +1065,36 @@ class Recompiler:
self._do_collect_type(self._global_type(tp, name))
def _generate_cpy_variable_decl(self, tp, name):
pass
prnt = self._prnt
tp = self._global_type(tp, name)
if isinstance(tp, model.ArrayType) and tp.length is None:
tp = tp.item
ampersand = ''
else:
ampersand = '&'
# This code assumes that casts from "tp *" to "void *" is a
# no-op, i.e. a function that returns a "tp *" can be called
# as if it returned a "void *". This should be generally true
# on any modern machine. The only exception to that rule (on
# uncommon architectures, and as far as I can tell) might be
# if 'tp' were a function type, but that is not possible here.
# (If 'tp' is a function _pointer_ type, then casts from "fn_t
# **" to "void *" are again no-ops, as far as I can tell.)
prnt('static ' + tp.get_c_name('*_cffi_var_%s(void)' % (name,)))
prnt('{')
prnt(' return %s(%s);' % (ampersand, name))
prnt('}')
prnt()
def _generate_cpy_variable_ctx(self, tp, name):
tp = self._global_type(tp, name)
type_index = self._typesdict[tp]
type_op = CffiOp(OP_GLOBAL_VAR, type_index)
if tp.sizeof_enabled():
size = "sizeof(%s)" % (name,)
if self.target_is_python:
op = OP_GLOBAL_VAR
else:
size = 0
op = OP_GLOBAL_VAR_F
self._lsts["global"].append(
GlobalExpr(name, '&%s' % name, type_op, size))
GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index)))
# ----------
# emitting the opcodes for individual types
......
......@@ -562,8 +562,6 @@ Known missing features that are GCC or MSVC extensions:
foo_wrapper(struct my_complex c) { foo(c.real + c.imag*1j); }``, and
call ``foo_wrapper`` rather than ``foo`` directly.
* Thread-local variables (access them via getter/setter functions)
* Function pointers with non-default calling conventions (e.g. on
Windows, "stdcall").
......@@ -577,6 +575,11 @@ believes it cannot ask the C compiler for the length of the array, you
get reduced safety checks: for example, you risk overwriting the
following fields by passing too many array items in the constructor.
.. versionadded:: 1.2
Thread-local variables (``__thread``) can be accessed, as well as
variables defined as dynamic macros (``#define myvar (*fetchme())``).
Before version 1.2, you need to write getter/setter functions.
Debugging dlopen'ed C libraries
-------------------------------
......
......@@ -30,6 +30,12 @@ What's New
dict---assuming that ``lib`` has got no symbol called precisely
``__dict__``. (In general, it is safer to use ``dir(lib)``.)
* Out-of-line API mode: global variables are now fetched on demand at
every access. It fixes issue #212 (Windows DLL variables), and also
allows variables that are defined as dynamic macros (like ``errno``)
or ``__thread`` -local variables. (This change might also tighten
the C compiler's check on the variables' type.)
* Issue #209: dereferencing NULL pointers now raises RuntimeError
instead of segfaulting. Meant as a debugging aid. The check is
only for NULL: if you dereference random or dead pointers you might
......
......@@ -511,22 +511,38 @@ def test_module_name_in_package():
def test_bad_size_of_global_1():
ffi = FFI()
ffi.cdef("short glob;")
lib = verify(ffi, "test_bad_size_of_global_1", "long glob;")
py.test.raises(ffi.error, "lib.glob")
py.test.raises(VerificationError, verify, ffi,
"test_bad_size_of_global_1", "long glob;")
def test_bad_size_of_global_2():
ffi = FFI()
ffi.cdef("int glob[10];")
lib = verify(ffi, "test_bad_size_of_global_2", "int glob[9];")
e = py.test.raises(ffi.error, "lib.glob")
assert str(e.value) == ("global variable 'glob' should be 40 bytes "
"according to the cdef, but is actually 36")
py.test.raises(VerificationError, verify, ffi,
"test_bad_size_of_global_2", "int glob[9];")
def test_unspecified_size_of_global():
def test_unspecified_size_of_global_1():
ffi = FFI()
ffi.cdef("int glob[];")
lib = verify(ffi, "test_unspecified_size_of_global", "int glob[10];")
lib.glob # does not crash
lib = verify(ffi, "test_unspecified_size_of_global_1", "int glob[10];")
assert ffi.typeof(lib.glob) == ffi.typeof("int *")
def test_unspecified_size_of_global_2():
ffi = FFI()
ffi.cdef("int glob[][5];")
lib = verify(ffi, "test_unspecified_size_of_global_2", "int glob[10][5];")
assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]")
def test_unspecified_size_of_global_3():
ffi = FFI()
ffi.cdef("int glob[][...];")
lib = verify(ffi, "test_unspecified_size_of_global_3", "int glob[10][5];")
assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]")
def test_unspecified_size_of_global_4():
ffi = FFI()
ffi.cdef("int glob[...][...];")
lib = verify(ffi, "test_unspecified_size_of_global_4", "int glob[10][5];")
assert ffi.typeof(lib.glob) == ffi.typeof("int[10][5]")
def test_include_1():
ffi1 = FFI()
......@@ -888,9 +904,11 @@ def test_constant_of_unknown_size():
typedef ... opaque_t;
const opaque_t CONSTANT;
""")
e = py.test.raises(VerificationError, verify, ffi,
'test_constant_of_unknown_size', "stuff")
assert str(e.value) == ("constant CONSTANT: constant 'CONSTANT' is of "
lib = verify(ffi, 'test_constant_of_unknown_size',
"typedef int opaque_t;"
"const int CONSTANT = 42;")
e = py.test.raises(ffi.error, getattr, lib, 'CONSTANT')
assert str(e.value) == ("constant 'CONSTANT' is of "
"type 'opaque_t', whose size is not known")
def test_variable_of_unknown_size():
......@@ -899,7 +917,7 @@ def test_variable_of_unknown_size():
typedef ... opaque_t;
opaque_t globvar;
""")
lib = verify(ffi, 'test_constant_of_unknown_size', """
lib = verify(ffi, 'test_variable_of_unknown_size', """
typedef char opaque_t[6];
opaque_t globvar = "hello";
""")
......@@ -1081,3 +1099,32 @@ def test_import_from_lib():
assert hasattr(lib, '__dict__')
assert lib.__all__ == ['MYFOO', 'mybar'] # but not 'myvar'
assert lib.__name__ == repr(lib)
def test_macro_var_callback():
ffi = FFI()
ffi.cdef("int my_value; int *(*get_my_value)(void);")
lib = verify(ffi, 'test_macro_var_callback',
"int *(*get_my_value)(void);\n"
"#define my_value (*get_my_value())")
#
values = ffi.new("int[50]")
def it():
for i in range(50):
yield i
it = it()
#
@ffi.callback("int *(*)(void)")
def get_my_value():
return values + it.next()
lib.get_my_value = get_my_value
#
values[0] = 41
assert lib.my_value == 41 # [0]
p = ffi.addressof(lib, 'my_value') # [1]
assert p == values + 1
assert p[-1] == 41
assert p[+1] == 0
lib.my_value = 42 # [2]
assert values[2] == 42
assert p[-1] == 41
assert p[+1] == 42
......@@ -2242,3 +2242,27 @@ def test_windows_dllimport_data():
lib = ffi.verify("extern __declspec(dllimport) int my_value;",
sources = [str(tmpfile)])
assert lib.my_value == 42
def test_macro_var():
ffi = FFI()
ffi.cdef("int myarray[50], my_value;")
lib = ffi.verify("""
int myarray[50];
int *get_my_value(void) {
static int index = 0;
return &myarray[index++];
}
#define my_value (*get_my_value())
""")
assert lib.my_value == 0 # [0]
lib.my_value = 42 # [1]
assert lib.myarray[1] == 42
assert lib.my_value == 0 # [2]
lib.myarray[3] = 63
assert lib.my_value == 63 # [3]
p = ffi.addressof(lib, 'my_value') # [4]
assert p[-1] == 63
assert p[0] == 0
assert p == lib.myarray + 4
p[1] = 82
assert lib.my_value == 82 # [5]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment