Author: Ronan Lamy <ronan.l...@gmail.com> Branch: py3.5 Changeset: r89644:573fc5d78d57 Date: 2017-01-17 18:44 +0000 http://bitbucket.org/pypy/pypy/changeset/573fc5d78d57/
Log: hg merge default diff too long, truncating to 2000 out of 2639 lines diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -128,3 +128,9 @@ Also fix for c-extension type that calls ``tp_hash`` during initialization (str, unicode types), and fix instantiating c-extension types from built-in classes by enforcing an order of instaniation. + +.. branch: rffi-parser-2 + +rffi structures in cpyext can now be created by parsing simple C headers. +Additionally, the cts object that holds the parsed information can act like +cffi's ffi objects, with the methods cts.cast() and cts.gettype(). diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -251,12 +251,11 @@ class GetSetProperty(W_Root): _immutable_fields_ = ["fget", "fset", "fdel"] - name = '<generic property>' w_objclass = None @specialize.arg(7) def __init__(self, fget, fset=None, fdel=None, doc=None, - cls=None, use_closure=False, tag=None): + cls=None, use_closure=False, tag=None, name=None): objclass_getter, cls = make_objclass_getter(tag, fget, cls) fget = make_descr_typecheck_wrapper((tag, 0), fget, cls=cls, use_closure=use_closure) @@ -264,9 +263,11 @@ cls=cls, use_closure=use_closure) fdel = make_descr_typecheck_wrapper((tag, 2), fdel, cls=cls, use_closure=use_closure) - self._init(fget, fset, fdel, doc, cls, objclass_getter, use_closure) + self._init(fget, fset, fdel, doc, cls, objclass_getter, use_closure, + name) - def _init(self, fget, fset, fdel, doc, cls, objclass_getter, use_closure): + def _init(self, fget, fset, fdel, doc, cls, objclass_getter, use_closure, + name): self.fget = fget self.fset = fset self.fdel = fdel @@ -275,13 +276,13 @@ self.qualname = None self.objclass_getter = objclass_getter self.use_closure = use_closure + self.name = name if name is not None else '<generic property>' def copy_for_type(self, w_objclass): if self.objclass_getter is None: new = instantiate(GetSetProperty) new._init(self.fget, self.fset, self.fdel, self.doc, self.reqcls, - None, self.use_closure) - new.name = self.name + None, self.use_closure, self.name) new.w_objclass = w_objclass return new else: diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -40,37 +40,35 @@ from rpython.rlib import rthread from rpython.rlib.debug import fatalerror_notb from pypy.objspace.std.typeobject import W_TypeObject, find_best_base +from pypy.module.cpyext.cparser import CTypeSpace DEBUG_WRAPPER = True -# update these for other platforms -Py_ssize_t = lltype.Typedef(rffi.SSIZE_T, 'Py_ssize_t') -Py_ssize_tP = rffi.CArrayPtr(Py_ssize_t) -size_t = rffi.ULONG -ADDR = lltype.Signed - pypydir = py.path.local(pypydir) include_dir = pypydir / 'module' / 'cpyext' / 'include' +parse_dir = pypydir / 'module' / 'cpyext' / 'parse' source_dir = pypydir / 'module' / 'cpyext' / 'src' translator_c_dir = py.path.local(cdir) include_dirs = [ include_dir, + parse_dir, translator_c_dir, udir, ] -class CConfig: - _compilation_info_ = ExternalCompilationInfo( +configure_eci = ExternalCompilationInfo( include_dirs=include_dirs, includes=['Python.h', 'stdarg.h', 'structmember.h'], - compile_extra=['-DPy_BUILD_CORE'], - ) + compile_extra=['-DPy_BUILD_CORE']) + +class CConfig: + _compilation_info_ = configure_eci class CConfig2: - _compilation_info_ = CConfig._compilation_info_ + _compilation_info_ = configure_eci class CConfig_constants: - _compilation_info_ = CConfig._compilation_info_ + _compilation_info_ = configure_eci CONST_STRING = lltype.Ptr(lltype.Array(lltype.Char, hints={'nolength': True}), @@ -117,6 +115,9 @@ return is_valid_fd(c_fileno(fp)) pypy_decl = 'pypy_decl.h' +udir.join(pypy_decl).write("/* Will be filled later */\n") +udir.join('pypy_structmember_decl.h').write("/* Will be filled later */\n") +udir.join('pypy_macros.h').write("/* Will be filled later */\n") constant_names = """ Py_TPFLAGS_READY Py_TPFLAGS_READYING Py_TPFLAGS_HAVE_GETCHARBUFFER @@ -129,9 +130,6 @@ """.split() for name in constant_names: setattr(CConfig_constants, name, rffi_platform.ConstantInteger(name)) -udir.join(pypy_decl).write("/* Will be filled later */\n") -udir.join('pypy_structmember_decl.h').write("/* Will be filled later */\n") -udir.join('pypy_macros.h').write("/* Will be filled later */\n") globals().update(rffi_platform.configure(CConfig_constants)) def _copy_header_files(headers, dstdir): @@ -145,12 +143,14 @@ target.chmod(0444) # make the file read-only, to make sure that nobody # edits it by mistake -def copy_header_files(dstdir, copy_numpy_headers): +def copy_header_files(cts, dstdir, copy_numpy_headers): # XXX: 20 lines of code to recursively copy a directory, really?? assert dstdir.check(dir=True) headers = include_dir.listdir('*.h') + include_dir.listdir('*.inl') for name in ["pypy_macros.h"] + FUNCTIONS_BY_HEADER.keys(): headers.append(udir.join(name)) + for path in cts.parsed_headers: + headers.append(path) _copy_header_files(headers, dstdir) if copy_numpy_headers: @@ -251,13 +251,15 @@ class ApiFunction(object): def __init__(self, argtypes, restype, callable, error=CANNOT_FAIL, - c_name=None, gil=None, result_borrowed=False, result_is_ll=False): + c_name=None, cdecl=None, gil=None, + result_borrowed=False, result_is_ll=False): self.argtypes = argtypes self.restype = restype self.functype = lltype.Ptr(lltype.FuncType(argtypes, restype)) self.callable = callable self.error_value = error self.c_name = c_name + self.cdecl = cdecl # extract the signature from the (CPython-level) code object from pypy.interpreter import pycode @@ -272,8 +274,6 @@ self.gil = gil self.result_borrowed = result_borrowed self.result_is_ll = result_is_ll - if result_is_ll: # means 'returns a low-level PyObject pointer' - assert is_PyObject(restype) # def get_llhelper(space): return llhelper(self.functype, self.get_wrapper(space)) @@ -381,6 +381,8 @@ return unwrapper def get_c_restype(self, c_writer): + if self.cdecl: + return self.cdecl.split(self.c_name)[0].strip() return c_writer.gettype(self.restype).replace('@', '').strip() def get_c_args(self, c_writer): @@ -480,6 +482,20 @@ return unwrapper return decorate +def api_decl(cdecl, cts, error=_NOT_SPECIFIED, header=DEFAULT_HEADER): + def decorate(func): + func._always_inline_ = 'try' + name, FUNC = cts.parse_func(cdecl) + api_function = ApiFunction( + FUNC.ARGS, FUNC.RESULT, func, + error=_compute_error(error, FUNC.RESULT), cdecl=cdecl) + FUNCTIONS_BY_HEADER[header][name] = api_function + unwrapper = api_function.get_unwrapper() + unwrapper.func = func + unwrapper.api_func = api_function + return unwrapper + return decorate + def slot_function(argtypes, restype, error=_NOT_SPECIFIED): def decorate(func): func._always_inline_ = 'try' @@ -658,41 +674,31 @@ % (cpyname, )) build_exported_objects() +cts = CTypeSpace(headers=['sys/types.h', 'stdarg.h', 'stdio.h']) +cts.parse_header(parse_dir / 'cpyext_object.h') + +Py_ssize_t = cts.gettype('Py_ssize_t') +Py_ssize_tP = cts.gettype('Py_ssize_t *') +size_t = rffi.ULONG +ADDR = lltype.Signed + # Note: as a special case, "PyObject" is the pointer type in RPython, # corresponding to "PyObject *" in C. We do that only for PyObject. # For example, "PyTypeObject" is the struct type even in RPython. -PyTypeObject = lltype.ForwardReference() -PyTypeObjectPtr = lltype.Ptr(PyTypeObject) -PyObjectStruct = lltype.ForwardReference() -PyObject = lltype.Ptr(PyObjectStruct) +PyTypeObject = cts.gettype('PyTypeObject') +PyTypeObjectPtr = cts.gettype('PyTypeObject *') +PyObjectStruct = cts.gettype('PyObject') +PyObject = cts.gettype('PyObject *') PyObjectFields = (("ob_refcnt", lltype.Signed), ("ob_pypy_link", lltype.Signed), ("ob_type", PyTypeObjectPtr)) PyVarObjectFields = PyObjectFields + (("ob_size", Py_ssize_t), ) -cpython_struct('PyObject', PyObjectFields, PyObjectStruct) -PyVarObjectStruct = cpython_struct("PyVarObject", PyVarObjectFields) -PyVarObject = lltype.Ptr(PyVarObjectStruct) +PyVarObjectStruct = cts.gettype('PyVarObject') +PyVarObject = cts.gettype('PyVarObject *') -Py_buffer = cpython_struct( - "Py_buffer", ( - ('buf', rffi.VOIDP), - ('obj', PyObject), - ('len', Py_ssize_t), - ('itemsize', Py_ssize_t), +Py_buffer = cts.gettype('Py_buffer') +Py_bufferP = cts.gettype('Py_buffer *') - ('readonly', rffi.INT_real), - ('ndim', rffi.INT_real), - ('format', rffi.CCHARP), - ('shape', Py_ssize_tP), - ('strides', Py_ssize_tP), - ('suboffsets', Py_ssize_tP), - ('_format', rffi.CFixedArray(rffi.UCHAR, Py_MAX_FMT)), - ('_shape', rffi.CFixedArray(Py_ssize_t, Py_MAX_NDIMS)), - ('_strides', rffi.CFixedArray(Py_ssize_t, Py_MAX_NDIMS)), - #('smalltable', rffi.CFixedArray(Py_ssize_t, 2)), - ('internal', rffi.VOIDP), -)) -Py_bufferP = lltype.Ptr(Py_buffer) @specialize.memo() def is_PyObject(TYPE): @@ -1415,7 +1421,7 @@ include_lines.append('RPY_EXPORTED %s %s;\n' % (typ, name)) lines.append('};\n') - eci2 = CConfig._compilation_info_.merge(ExternalCompilationInfo( + eci2 = configure_eci.merge(ExternalCompilationInfo( separate_module_sources = [''.join(lines)], post_include_bits = [''.join(include_lines)], )) @@ -1433,7 +1439,7 @@ setup_init_functions(eci, prefix) trunk_include = pypydir.dirpath() / 'include' - copy_header_files(trunk_include, use_micronumpy) + copy_header_files(cts, trunk_include, use_micronumpy) def _load_from_cffi(space, name, path, initptr): diff --git a/pypy/module/cpyext/cparser.py b/pypy/module/cpyext/cparser.py new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/cparser.py @@ -0,0 +1,879 @@ +from collections import OrderedDict +from cffi import api, model +from cffi.commontypes import COMMON_TYPES, resolve_common_type +try: + from cffi import _pycparser as pycparser +except ImportError: + import pycparser +import weakref, re +from rpython.translator.tool.cbuild import ExternalCompilationInfo +from rpython.rlib.rfile import FILEP +from rpython.rtyper.lltypesystem import rffi, lltype +from rpython.rtyper.tool import rfficache, rffi_platform +from rpython.flowspace.model import Constant, const +from rpython.flowspace.specialcase import register_flow_sc +from rpython.flowspace.flowcontext import FlowingError + +_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", + re.DOTALL | re.MULTILINE) +_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" + r"\b((?:[^\n\\]|\\.)*?)$", + re.DOTALL | re.MULTILINE) +_r_words = re.compile(r"\w+|\S") +_parser_cache = None +_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) +_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") +_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") +_r_cdecl = re.compile(r"\b__cdecl\b") +_r_star_const_space = re.compile( # matches "* const " + r"[*]\s*((const|volatile|restrict)\b\s*)+") + +def _get_parser(): + global _parser_cache + if _parser_cache is None: + _parser_cache = pycparser.CParser() + return _parser_cache + +def _preprocess(csource, macros): + # Remove comments. NOTE: this only work because the cdef() section + # should not contain any string literal! + csource = _r_comment.sub(' ', csource) + # Remove the "#define FOO x" lines + for match in _r_define.finditer(csource): + macroname, macrovalue = match.groups() + macrovalue = macrovalue.replace('\\\n', '').strip() + macros[macroname] = macrovalue + csource = _r_define.sub('', csource) + # + # BIG HACK: replace WINAPI or __stdcall with "volatile const". + # It doesn't make sense for the return type of a function to be + # "volatile volatile const", so we abuse it to detect __stdcall... + # Hack number 2 is that "int(volatile *fptr)();" is not valid C + # syntax, so we place the "volatile" before the opening parenthesis. + csource = _r_stdcall2.sub(' volatile volatile const(', csource) + csource = _r_stdcall1.sub(' volatile volatile const ', csource) + csource = _r_cdecl.sub(' ', csource) + + for name, value in reversed(macros.items()): + csource = re.sub(r'\b%s\b' % name, value, csource) + + return csource, macros + +def _common_type_names(csource): + # Look in the source for what looks like usages of types from the + # list of common types. A "usage" is approximated here as the + # appearance of the word, minus a "definition" of the type, which + # is the last word in a "typedef" statement. Approximative only + # but should be fine for all the common types. + look_for_words = set(COMMON_TYPES) + look_for_words.add(';') + look_for_words.add(',') + look_for_words.add('(') + look_for_words.add(')') + look_for_words.add('typedef') + words_used = set() + is_typedef = False + paren = 0 + previous_word = '' + for word in _r_words.findall(csource): + if word in look_for_words: + if word == ';': + if is_typedef: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + is_typedef = False + elif word == 'typedef': + is_typedef = True + paren = 0 + elif word == '(': + paren += 1 + elif word == ')': + paren -= 1 + elif word == ',': + if is_typedef and paren == 0: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + else: # word in COMMON_TYPES + words_used.add(word) + previous_word = word + return words_used + + +class Parser(object): + + def __init__(self): + self._declarations = {} + self._included_declarations = set() + self._anonymous_counter = 0 + self._structnode2type = weakref.WeakKeyDictionary() + self._options = {} + self._int_constants = {} + self._recomplete = [] + self._macros = OrderedDict() + + def _parse(self, csource): + # modifies self._macros in-place + csource, macros = _preprocess(csource, self._macros) + # XXX: for more efficiency we would need to poke into the + # internals of CParser... the following registers the + # typedefs, because their presence or absence influences the + # parsing itself (but what they are typedef'ed to plays no role) + ctn = _common_type_names(csource) + typenames = [] + for name in sorted(self._declarations): + if name.startswith('typedef '): + name = name[8:] + typenames.append(name) + ctn.discard(name) + typenames += sorted(ctn) + # + csourcelines = ['typedef int %s;' % typename for typename in typenames] + csourcelines.append('typedef int __dotdotdot__;') + csourcelines.append(csource) + csource = '\n'.join(csourcelines) + try: + ast = _get_parser().parse(csource) + except pycparser.c_parser.ParseError as e: + self.convert_pycparser_error(e, csource) + # csource will be used to find buggy source text + return ast, macros, csource + + def _convert_pycparser_error(self, e, csource): + # xxx look for ":NUM:" at the start of str(e) and try to interpret + # it as a line number + line = None + msg = str(e) + if msg.startswith(':') and ':' in msg[1:]: + linenum = msg[1:msg.find(':',1)] + if linenum.isdigit(): + linenum = int(linenum, 10) + csourcelines = csource.splitlines() + if 1 <= linenum <= len(csourcelines): + line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) + if line: + msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) + else: + msg = 'parse error\n%s' % (msg,) + raise api.CDefError(msg) + + def parse(self, csource, override=False, packed=False, dllexport=False): + prev_options = self._options + try: + self._options = {'override': override, + 'packed': packed, + 'dllexport': dllexport} + self._internal_parse(csource) + finally: + self._options = prev_options + + def _internal_parse(self, csource): + ast, macros, csource = self._parse(csource) + # add the macros + self._process_macros(macros) + # find the first "__dotdotdot__" and use that as a separator + # between the repeated typedefs and the real csource + iterator = iter(ast.ext) + for decl in iterator: + if decl.name == '__dotdotdot__': + break + # + try: + for decl in iterator: + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise api.CDefError("typedef does not declare any name", + decl) + quals = 0 + realtype, quals = self._get_type_and_quals( + decl.type, name=decl.name, partial_length_ok=True) + self._declare('typedef ' + decl.name, realtype, quals=quals) + elif decl.__class__.__name__ == 'Pragma': + pass # skip pragma, only in pycparser 2.15 + else: + raise api.CDefError("unrecognized construct", decl) + except api.FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + if self._int_constants[key] == val: + return # ignore identical double declarations + raise api.FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _add_integer_constant(self, name, int_str): + int_str = int_str.lower().rstrip("ul") + neg = int_str.startswith('-') + if neg: + int_str = int_str[1:] + # "010" is not valid oct in py3 + if (int_str.startswith("0") and int_str != '0' + and not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + pyvalue = int(int_str, 0) + if neg: + pyvalue = -pyvalue + self._add_constants(name, pyvalue) + self._declare('macro ' + name, pyvalue) + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + if _r_int_literal.match(value): + self._add_integer_constant(key, value) + else: + self._declare('macro ' + key, value) + + def _declare_function(self, tp, quals, decl): + tp = self._get_type_pointer(tp, quals) + if self._options.get('dllexport'): + tag = 'dllexport_python ' + else: + tag = 'function ' + self._declare(tag + decl.name, tp) + + def _parse_decl(self, decl): + node = decl.type + if isinstance(node, pycparser.c_ast.FuncDecl): + tp, quals = self._get_type_and_quals(node, name=decl.name) + assert isinstance(tp, model.RawFunctionType) + self._declare_function(tp, quals, decl) + else: + if isinstance(node, pycparser.c_ast.Struct): + self._get_struct_union_enum_type('struct', node) + elif isinstance(node, pycparser.c_ast.Union): + self._get_struct_union_enum_type('union', node) + elif isinstance(node, pycparser.c_ast.Enum): + self._get_struct_union_enum_type('enum', node) + elif not decl.name: + raise api.CDefError("construct does not declare any variable", + decl) + # + if decl.name: + tp, quals = self._get_type_and_quals(node, + partial_length_ok=True) + if tp.is_raw_function: + self._declare_function(tp, quals, decl) + elif (tp.is_integer_type() and + hasattr(decl, 'init') and + hasattr(decl.init, 'value') and + _r_int_literal.match(decl.init.value)): + self._add_integer_constant(decl.name, decl.init.value) + elif (tp.is_integer_type() and + isinstance(decl.init, pycparser.c_ast.UnaryOp) and + decl.init.op == '-' and + hasattr(decl.init.expr, 'value') and + _r_int_literal.match(decl.init.expr.value)): + self._add_integer_constant(decl.name, + '-' + decl.init.expr.value) + else: + if (quals & model.Q_CONST) and not tp.is_array_type: + self._declare('constant ' + decl.name, tp, quals=quals) + else: + self._declare('variable ' + decl.name, tp, quals=quals) + + def parse_type(self, cdecl): + return self.parse_type_and_quals(cdecl)[0] + + def parse_type_and_quals(self, cdecl): + ast, _, _ = self._parse('void __dummy(\n%s\n);' % cdecl) + exprnode = ast.ext[-1].type.args.params[0] + if isinstance(exprnode, pycparser.c_ast.ID): + raise api.CDefError("unknown identifier '%s'" % (exprnode.name,)) + return self._get_type_and_quals(exprnode.type) + + def _declare(self, name, obj, included=False, quals=0): + if name in self._declarations: + prevobj, prevquals = self._declarations[name] + if prevobj is obj and prevquals == quals: + return + self._declarations[name] = (obj, quals) + if included: + self._included_declarations.add(obj) + + def _extract_quals(self, type): + quals = 0 + if isinstance(type, (pycparser.c_ast.TypeDecl, + pycparser.c_ast.PtrDecl)): + if 'const' in type.quals: + quals |= model.Q_CONST + if 'volatile' in type.quals: + quals |= model.Q_VOLATILE + if 'restrict' in type.quals: + quals |= model.Q_RESTRICT + return quals + + def _get_type_pointer(self, type, quals, declname=None): + if isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname, quals) + return model.PointerType(type, quals) + + def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False): + # first, dereference typedefs, if we have it already parsed, we're good + if (isinstance(typenode, pycparser.c_ast.TypeDecl) and + isinstance(typenode.type, pycparser.c_ast.IdentifierType) and + len(typenode.type.names) == 1 and + ('typedef ' + typenode.type.names[0]) in self._declarations): + tp, quals = self._declarations['typedef ' + typenode.type.names[0]] + quals |= self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.ArrayDecl): + # array type + if typenode.dim is None: + length = None + else: + length = self._parse_constant( + typenode.dim, partial_length_ok=partial_length_ok) + tp, quals = self._get_type_and_quals(typenode.type, + partial_length_ok=partial_length_ok) + return model.ArrayType(tp, length), quals + # + if isinstance(typenode, pycparser.c_ast.PtrDecl): + # pointer type + itemtype, itemquals = self._get_type_and_quals(typenode.type) + tp = self._get_type_pointer(itemtype, itemquals, declname=name) + quals = self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.TypeDecl): + quals = self._extract_quals(typenode) + type = typenode.type + if isinstance(type, pycparser.c_ast.IdentifierType): + # assume a primitive type. get it from .names, but reduce + # synonyms to a single chosen combination + names = list(type.names) + if names != ['signed', 'char']: # keep this unmodified + prefixes = {} + while names: + name = names[0] + if name in ('short', 'long', 'signed', 'unsigned'): + prefixes[name] = prefixes.get(name, 0) + 1 + del names[0] + else: + break + # ignore the 'signed' prefix below, and reorder the others + newnames = [] + for prefix in ('unsigned', 'short', 'long'): + for i in range(prefixes.get(prefix, 0)): + newnames.append(prefix) + if not names: + names = ['int'] # implicitly + if names == ['int']: # but kill it if 'short' or 'long' + if 'short' in prefixes or 'long' in prefixes: + names = [] + names = newnames + names + ident = ' '.join(names) + if ident == 'void': + return model.void_type, quals + tp0, quals0 = resolve_common_type(self, ident) + return tp0, (quals | quals0) + # + if isinstance(type, pycparser.c_ast.Struct): + # 'struct foobar' + tp = self._get_struct_union_enum_type('struct', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Union): + # 'union foobar' + tp = self._get_struct_union_enum_type('union', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Enum): + # 'enum foobar' + tp = self._get_struct_union_enum_type('enum', type, name) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.FuncDecl): + # a function type + return self._parse_function_type(typenode, name), 0 + # + # nested anonymous structs or unions end up here + if isinstance(typenode, pycparser.c_ast.Struct): + return self._get_struct_union_enum_type('struct', typenode, name, + nested=True), 0 + if isinstance(typenode, pycparser.c_ast.Union): + return self._get_struct_union_enum_type('union', typenode, name, + nested=True), 0 + # + raise api.FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) + + def _parse_function_type(self, typenode, funcname=None): + params = list(getattr(typenode.args, 'params', [])) + for i, arg in enumerate(params): + if not hasattr(arg, 'type'): + raise api.CDefError("%s arg %d: unknown type '%s'" + " (if you meant to use the old C syntax of giving" + " untyped arguments, it is not supported)" + % (funcname or 'in expression', i + 1, + getattr(arg, 'name', '?'))) + ellipsis = ( + len(params) > 0 and + isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and + isinstance(params[-1].type.type, + pycparser.c_ast.IdentifierType) and + params[-1].type.type.names == ['__dotdotdot__']) + if ellipsis: + params.pop() + if not params: + raise api.CDefError( + "%s: a function with only '(...)' as argument" + " is not correct C" % (funcname or 'in expression')) + args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) + for argdeclnode in params] + if not ellipsis and args == [model.void_type]: + args = [] + result, quals = self._get_type_and_quals(typenode.type) + # the 'quals' on the result type are ignored. HACK: we absure them + # to detect __stdcall functions: we textually replace "__stdcall" + # with "volatile volatile const" above. + abi = None + if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway + if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: + abi = '__stdcall' + return model.RawFunctionType(tuple(args), result, ellipsis, abi) + + def _as_func_arg(self, type, quals): + if isinstance(type, model.ArrayType): + return model.PointerType(type.item, quals) + elif isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + else: + return type + + def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): + # First, a level of caching on the exact 'type' node of the AST. + # This is obscure, but needed because pycparser "unrolls" declarations + # such as "typedef struct { } foo_t, *foo_p" and we end up with + # an AST that is not a tree, but a DAG, with the "type" node of the + # two branches foo_t and foo_p of the trees being the same node. + # It's a bit silly but detecting "DAG-ness" in the AST tree seems + # to be the only way to distinguish this case from two independent + # structs. See test_struct_with_two_usages. + try: + return self._structnode2type[type] + except KeyError: + pass + # + # Note that this must handle parsing "struct foo" any number of + # times and always return the same StructType object. Additionally, + # one of these times (not necessarily the first), the fields of + # the struct can be specified with "struct foo { ...fields... }". + # If no name is given, then we have to create a new anonymous struct + # with no caching; in this case, the fields are either specified + # right now or never. + # + force_name = name + name = type.name + # + # get the type or create it if needed + if name is None: + # 'force_name' is used to guess a more readable name for + # anonymous structs, for the common case "typedef struct { } foo". + if force_name is not None: + explicit_name = '$%s' % force_name + else: + self._anonymous_counter += 1 + explicit_name = '$%d' % self._anonymous_counter + tp = None + else: + explicit_name = name + key = '%s %s' % (kind, name) + tp, _ = self._declarations.get(key, (None, None)) + # + if tp is None: + if kind == 'struct': + tp = model.StructType(explicit_name, None, None, None) + elif kind == 'union': + tp = model.UnionType(explicit_name, None, None, None) + elif kind == 'enum': + tp = self._build_enum_type(explicit_name, type.values) + else: + raise AssertionError("kind = %r" % (kind,)) + if name is not None: + self._declare(key, tp) + else: + if kind == 'enum' and type.values is not None: + raise NotImplementedError( + "enum %s: the '{}' declaration should appear on the first " + "time the enum is mentioned, not later" % explicit_name) + if not tp.forcename: + tp.force_the_name(force_name) + if tp.forcename and '$' in tp.name: + self._declare('anonymous %s' % tp.forcename, tp) + # + self._structnode2type[type] = tp + # + # enums: done here + if kind == 'enum': + return tp + # + # is there a 'type.decls'? If yes, then this is the place in the + # C sources that declare the fields. If no, then just return the + # existing type, possibly still incomplete. + if type.decls is None: + return tp + # + if tp.fldnames is not None: + raise api.CDefError("duplicate declaration of struct %s" % name) + fldnames = [] + fldtypes = [] + fldbitsize = [] + fldquals = [] + for decl in type.decls: + if decl.bitsize is None: + bitsize = -1 + else: + bitsize = self._parse_constant(decl.bitsize) + self._partial_length = False + type, fqual = self._get_type_and_quals(decl.type, + partial_length_ok=True) + if self._partial_length: + self._make_partial(tp, nested) + if isinstance(type, model.StructType) and type.partial: + self._make_partial(tp, nested) + fldnames.append(decl.name or '') + fldtypes.append(type) + fldbitsize.append(bitsize) + fldquals.append(fqual) + tp.fldnames = tuple(fldnames) + tp.fldtypes = tuple(fldtypes) + tp.fldbitsize = tuple(fldbitsize) + tp.fldquals = tuple(fldquals) + if fldbitsize != [-1] * len(fldbitsize): + if isinstance(tp, model.StructType) and tp.partial: + raise NotImplementedError("%s: using both bitfields and '...;'" + % (tp,)) + tp.packed = self._options.get('packed') + if tp.completed: # must be re-completed: it is not opaque any more + tp.completed = 0 + self._recomplete.append(tp) + return tp + + def _make_partial(self, tp, nested): + if not isinstance(tp, model.StructOrUnion): + raise api.CDefError("%s cannot be partial" % (tp,)) + if not tp.has_c_name() and not nested: + raise NotImplementedError("%s is partial but has no C name" %(tp,)) + tp.partial = True + + def _parse_constant(self, exprnode, partial_length_ok=False): + # for now, limited to expressions that are an immediate number + # or positive/negative number + if isinstance(exprnode, pycparser.c_ast.Constant): + s = exprnode.value + if s.startswith('0'): + if s.startswith('0x') or s.startswith('0X'): + return int(s, 16) + return int(s, 8) + elif '1' <= s[0] <= '9': + return int(s, 10) + elif s[0] == "'" and s[-1] == "'" and ( + len(s) == 3 or (len(s) == 4 and s[1] == "\\")): + return ord(s[-2]) + else: + raise api.CDefError("invalid constant %r" % (s,)) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '+'): + return self._parse_constant(exprnode.expr) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '-'): + return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] + # + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name == '__dotdotdotarray__'): + if partial_length_ok: + self._partial_length = True + return '...' + raise api.FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) + # + raise api.FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) + + def _build_enum_type(self, explicit_name, decls): + if decls is not None: + partial = False + enumerators = [] + enumvalues = [] + nextenumvalue = 0 + for enum in decls.enumerators: + if enum.value is not None: + nextenumvalue = self._parse_constant(enum.value) + enumerators.append(enum.name) + enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) + nextenumvalue += 1 + enumerators = tuple(enumerators) + enumvalues = tuple(enumvalues) + tp = model.EnumType(explicit_name, enumerators, enumvalues) + tp.partial = partial + else: # opaque enum + tp = model.EnumType(explicit_name, (), ()) + return tp + + def include(self, other): + for name, (tp, quals) in other._declarations.items(): + if name.startswith('anonymous $enum_$'): + continue # fix for test_anonymous_enum_include + kind = name.split(' ', 1)[0] + if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef', 'macro'): + self._declare(name, tp, included=True, quals=quals) + for k, v in other._int_constants.items(): + self._add_constants(k, v) + for k, v in other._macros.items(): + self._macros[k] = v + +CNAME_TO_LLTYPE = { + 'char': rffi.CHAR, + 'double': rffi.DOUBLE, 'long double': rffi.LONGDOUBLE, + 'float': rffi.FLOAT, 'FILE': FILEP.TO} + +def add_inttypes(): + for name in rffi.TYPES: + if name.startswith('unsigned'): + rname = 'u' + name[9:] + else: + rname = name + rname = rname.replace(' ', '').upper() + CNAME_TO_LLTYPE[name] = rfficache.platform.types[rname] + +add_inttypes() +CNAME_TO_LLTYPE['int'] = rffi.INT_real + +def cname_to_lltype(name): + return CNAME_TO_LLTYPE[name] + +class DelayedStruct(object): + def __init__(self, name, fields, TYPE): + self.struct_name = name + self.type_name = None + self.fields = fields + self.TYPE = TYPE + + def get_type_name(self): + if self.type_name is not None: + return self.type_name + elif not self.struct_name.startswith('$'): + return 'struct %s' % self.struct_name + else: + raise ValueError('Anonymous struct') + + def __repr__(self): + return "<struct {struct_name}>".format(**vars(self)) + + +class CTypeSpace(object): + def __init__(self, parser=None, definitions=None, macros=None, + headers=None, includes=None): + self.definitions = definitions if definitions is not None else {} + self.macros = macros if macros is not None else {} + self.structs = {} + self.ctx = parser if parser else Parser() + self.headers = headers if headers is not None else ['sys/types.h'] + self.parsed_headers = [] + self.sources = [] + self._Config = type('Config', (object,), {}) + self._TYPES = {} + self.includes = [] + self.struct_typedefs = {} + self._handled = set() + self._frozen = False + if includes is not None: + for header in includes: + self.include(header) + + def include(self, other): + self.ctx.include(other.ctx) + self.structs.update(other.structs) + self.includes.append(other) + + def parse_source(self, source): + self.sources.append(source) + self.ctx.parse(source) + self.configure_types() + + def parse_header(self, header_path): + self.headers.append(str(header_path)) + self.parsed_headers.append(header_path) + self.ctx.parse(header_path.read()) + self.configure_types() + + def add_typedef(self, name, obj, quals): + assert name not in self.definitions + tp = self.convert_type(obj, quals) + if isinstance(tp, DelayedStruct): + if tp.type_name is None: + tp.type_name = name + tp = self.realize_struct(tp) + self.definitions[name] = tp + + def add_macro(self, name, value): + assert name not in self.macros + self.macros[name] = value + + def new_struct(self, obj): + if obj.name == '_IO_FILE': # cffi weirdness + return cname_to_lltype('FILE') + struct = DelayedStruct(obj.name, None, lltype.ForwardReference()) + # Cache it early, to avoid infinite recursion + self.structs[obj] = struct + if obj.fldtypes is not None: + struct.fields = zip( + obj.fldnames, + [self.convert_field(field) for field in obj.fldtypes]) + return struct + + def convert_field(self, obj): + tp = self.convert_type(obj) + if isinstance(tp, DelayedStruct): + tp = tp.TYPE + return tp + + def realize_struct(self, struct): + type_name = struct.get_type_name() + configname = type_name.replace(' ', '__') + setattr(self._Config, configname, + rffi_platform.Struct(type_name, struct.fields)) + self._TYPES[configname] = struct.TYPE + return struct.TYPE + + def build_eci(self): + all_sources = [] + for cts in self.includes: + all_sources.extend(cts.sources) + all_sources.extend(self.sources) + all_headers = self.headers + for x in self.includes: + for hdr in x.headers: + if hdr not in all_headers: + all_headers.append(hdr) + return ExternalCompilationInfo( + post_include_bits=all_sources, includes=all_headers) + + def configure_types(self): + for name, (obj, quals) in self.ctx._declarations.iteritems(): + if obj in self.ctx._included_declarations: + continue + if name in self._handled: + continue + self._handled.add(name) + if name.startswith('typedef '): + name = name[8:] + self.add_typedef(name, obj, quals) + elif name.startswith('macro '): + name = name[6:] + self.add_macro(name, obj) + self._Config._compilation_info_ = self.build_eci() + for name, TYPE in rffi_platform.configure(self._Config).iteritems(): + # hack: prevent the source from being pasted into common_header.h + del TYPE._hints['eci'] + if name in self._TYPES: + self._TYPES[name].become(TYPE) + del self._TYPES[name] + + def convert_type(self, obj, quals=0): + if isinstance(obj, model.PrimitiveType): + return cname_to_lltype(obj.name) + elif isinstance(obj, model.StructType): + if obj in self.structs: + return self.structs[obj] + return self.new_struct(obj) + elif isinstance(obj, model.PointerType): + TO = self.convert_type(obj.totype) + if TO is lltype.Void: + return rffi.VOIDP + elif isinstance(TO, DelayedStruct): + TO = TO.TYPE + if isinstance(TO, lltype.ContainerType): + return lltype.Ptr(TO) + else: + if obj.quals & model.Q_CONST: + return lltype.Ptr(lltype.Array( + TO, hints={'nolength': True, 'render_as_const': True})) + else: + return rffi.CArrayPtr(TO) + elif isinstance(obj, model.FunctionPtrType): + if obj.ellipsis: + raise NotImplementedError + args = [self.convert_type(arg) for arg in obj.args] + res = self.convert_type(obj.result) + return lltype.Ptr(lltype.FuncType(args, res)) + elif isinstance(obj, model.VoidType): + return lltype.Void + elif isinstance(obj, model.ArrayType): + return rffi.CFixedArray(self.convert_type(obj.item), obj.length) + else: + raise NotImplementedError + + def gettype(self, cdecl): + obj = self.ctx.parse_type(cdecl) + result = self.convert_type(obj) + if isinstance(result, DelayedStruct): + result = result.TYPE + return result + + def cast(self, cdecl, value): + return rffi.cast(self.gettype(cdecl), value) + + def parse_func(self, cdecl): + cdecl = cdecl.strip() + if cdecl[-1] != ';': + cdecl += ';' + ast, _, _ = self.ctx._parse(cdecl) + decl = ast.ext[-1] + tp, quals = self.ctx._get_type_and_quals(decl.type, name=decl.name) + FUNCP = self.convert_type(tp.as_function_pointer()) + return decl.name, FUNCP.TO + + def _freeze_(self): + if self._frozen: + return True + + @register_flow_sc(self.cast) + def sc_cast(ctx, v_decl, v_arg): + if not isinstance(v_decl, Constant): + raise FlowingError( + "The first argument of cts.cast() must be a constant.") + TP = self.gettype(v_decl.value) + return ctx.appcall(rffi.cast, const(TP), v_arg) + + @register_flow_sc(self.gettype) + def sc_gettype(ctx, v_decl): + if not isinstance(v_decl, Constant): + raise FlowingError( + "The argument of cts.gettype() must be a constant.") + return const(self.gettype(v_decl.value)) + + self._frozen = True + return True + + +def parse_source(source, includes=None, headers=None, configure_now=True): + cts = CTypeSpace(headers=headers, includes=includes) + cts.parse_source(source) + return cts diff --git a/pypy/module/cpyext/include/descrobject.h b/pypy/module/cpyext/include/descrobject.h --- a/pypy/module/cpyext/include/descrobject.h +++ b/pypy/module/cpyext/include/descrobject.h @@ -1,16 +1,5 @@ #ifndef Py_DESCROBJECT_H #define Py_DESCROBJECT_H -typedef PyObject *(*getter)(PyObject *, void *); -typedef int (*setter)(PyObject *, PyObject *, void *); - -typedef struct PyGetSetDef { - char *name; - getter get; - setter set; - char *doc; - void *closure; -} PyGetSetDef; - #define PyDescr_COMMON \ PyObject_HEAD \ diff --git a/pypy/module/cpyext/include/methodobject.h b/pypy/module/cpyext/include/methodobject.h --- a/pypy/module/cpyext/include/methodobject.h +++ b/pypy/module/cpyext/include/methodobject.h @@ -7,20 +7,6 @@ extern "C" { #endif -typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); -typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, - PyObject *); -typedef PyObject *(*PyNoArgsFunction)(PyObject *); - -struct PyMethodDef { - const char *ml_name; /* The name of the built-in function/method */ - PyCFunction ml_meth; /* The C function that implements it */ - int ml_flags; /* Combination of METH_xxx flags, which mostly - describe the args expected by the C func */ - const char *ml_doc; /* The __doc__ attribute, or NULL */ -}; -typedef struct PyMethodDef PyMethodDef; - typedef struct { PyObject_HEAD diff --git a/pypy/module/cpyext/include/object.h b/pypy/module/cpyext/include/object.h --- a/pypy/module/cpyext/include/object.h +++ b/pypy/module/cpyext/include/object.h @@ -9,6 +9,7 @@ #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#include <cpyext_object.h> /* CPython has this for backwards compatibility with really old extensions, and now @@ -16,29 +17,12 @@ */ #define staticforward static -#define PyObject_HEAD \ - Py_ssize_t ob_refcnt; \ - Py_ssize_t ob_pypy_link; \ - struct _typeobject *ob_type; - -#define PyObject_VAR_HEAD \ - PyObject_HEAD \ - Py_ssize_t ob_size; /* Number of items in variable part */ - #define PyObject_HEAD_INIT(type) \ 1, 0, type, #define PyVarObject_HEAD_INIT(type, size) \ PyObject_HEAD_INIT(type) size, -typedef struct _object { - PyObject_HEAD -} PyObject; - -typedef struct { - PyObject_VAR_HEAD -} PyVarObject; - #ifdef PYPY_DEBUG_REFCOUNT /* Slow version, but useful for debugging */ #define Py_INCREF(ob) (Py_IncRef((PyObject *)(ob))) @@ -94,65 +78,8 @@ #define Py_GT 4 #define Py_GE 5 -struct _typeobject; -typedef void (*freefunc)(void *); -typedef void (*destructor)(PyObject *); -typedef int (*printfunc)(PyObject *, FILE *, int); -typedef PyObject *(*getattrfunc)(PyObject *, char *); -typedef PyObject *(*getattrofunc)(PyObject *, PyObject *); -typedef int (*setattrfunc)(PyObject *, char *, PyObject *); -typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *); -typedef int (*cmpfunc)(PyObject *, PyObject *); -typedef PyObject *(*reprfunc)(PyObject *); -typedef long (*hashfunc)(PyObject *); -typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int); -typedef PyObject *(*getiterfunc) (PyObject *); -typedef PyObject *(*iternextfunc) (PyObject *); -typedef PyObject *(*descrgetfunc) (PyObject *, PyObject *, PyObject *); -typedef int (*descrsetfunc) (PyObject *, PyObject *, PyObject *); -typedef int (*initproc)(PyObject *, PyObject *, PyObject *); -typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *); -typedef PyObject *(*allocfunc)(struct _typeobject *, Py_ssize_t); - -typedef PyObject * (*unaryfunc)(PyObject *); -typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); -typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); -typedef int (*inquiry)(PyObject *); -typedef Py_ssize_t (*lenfunc)(PyObject *); -typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); -typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); -typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); -typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); -typedef int(*objobjargproc)(PyObject *, PyObject *, PyObject *); - /* Py3k buffer interface, adapted for PyPy */ -#define Py_MAX_NDIMS 32 -#define Py_MAX_FMT 128 -typedef struct bufferinfo { - void *buf; - PyObject *obj; /* owned reference */ - Py_ssize_t len; - Py_ssize_t itemsize; /* This is Py_ssize_t so it can be - pointed to by strides in simple case.*/ - int readonly; - int ndim; - char *format; - Py_ssize_t *shape; - Py_ssize_t *strides; - Py_ssize_t *suboffsets; /* alway NULL for app-level objects*/ - unsigned char _format[Py_MAX_FMT]; - Py_ssize_t _strides[Py_MAX_NDIMS]; - Py_ssize_t _shape[Py_MAX_NDIMS]; - /* static store for shape and strides of - mono-dimensional buffers. */ - /* Py_ssize_t smalltable[2]; */ - void *internal; /* always NULL for app-level objects */ -} Py_buffer; - -typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); -typedef void (*releasebufferproc)(PyObject *, Py_buffer *); - /* Flags for getting buffers */ #define PyBUF_SIMPLE 0 #define PyBUF_WRITABLE 0x0001 @@ -184,180 +111,7 @@ #define PyBUF_SHADOW 0x400 /* end Py3k buffer interface */ -typedef int (*objobjproc)(PyObject *, PyObject *); -typedef int (*visitproc)(PyObject *, void *); -typedef int (*traverseproc)(PyObject *, visitproc, void *); - - -typedef struct { - /* Number implementations must check *both* - arguments for proper type and implement the necessary conversions - in the slot functions themselves. */ - - binaryfunc nb_add; - binaryfunc nb_subtract; - binaryfunc nb_multiply; - binaryfunc nb_remainder; - binaryfunc nb_divmod; - ternaryfunc nb_power; - unaryfunc nb_negative; - unaryfunc nb_positive; - unaryfunc nb_absolute; - inquiry nb_bool; - unaryfunc nb_invert; - binaryfunc nb_lshift; - binaryfunc nb_rshift; - binaryfunc nb_and; - binaryfunc nb_xor; - binaryfunc nb_or; - unaryfunc nb_int; - void *nb_reserved; /* the slot formerly known as nb_long */ - unaryfunc nb_float; - - binaryfunc nb_inplace_add; - binaryfunc nb_inplace_subtract; - binaryfunc nb_inplace_multiply; - binaryfunc nb_inplace_remainder; - ternaryfunc nb_inplace_power; - binaryfunc nb_inplace_lshift; - binaryfunc nb_inplace_rshift; - binaryfunc nb_inplace_and; - binaryfunc nb_inplace_xor; - binaryfunc nb_inplace_or; - - binaryfunc nb_floor_divide; - binaryfunc nb_true_divide; - binaryfunc nb_inplace_floor_divide; - binaryfunc nb_inplace_true_divide; - - unaryfunc nb_index; - - binaryfunc nb_matrix_multiply; - binaryfunc nb_inplace_matrix_multiply; -} PyNumberMethods; - -typedef struct { - lenfunc sq_length; - binaryfunc sq_concat; - ssizeargfunc sq_repeat; - ssizeargfunc sq_item; - void *was_sq_slice; - ssizeobjargproc sq_ass_item; - void *was_sq_ass_slice; - objobjproc sq_contains; - - binaryfunc sq_inplace_concat; - ssizeargfunc sq_inplace_repeat; -} PySequenceMethods; - -typedef struct { - lenfunc mp_length; - binaryfunc mp_subscript; - objobjargproc mp_ass_subscript; -} PyMappingMethods; - -typedef struct { - unaryfunc am_await; - unaryfunc am_aiter; - unaryfunc am_anext; -} PyAsyncMethods; - -typedef struct { - getbufferproc bf_getbuffer; - releasebufferproc bf_releasebuffer; -} PyBufferProcs; - - -typedef struct _typeobject { - PyObject_VAR_HEAD - const char *tp_name; /* For printing, in format "<module>.<name>" */ - Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ - - /* Methods to implement standard operations */ - - destructor tp_dealloc; - printfunc tp_print; - getattrfunc tp_getattr; - setattrfunc tp_setattr; - PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) - or tp_reserved (Python 3) */ - reprfunc tp_repr; - - /* Method suites for standard classes */ - - PyNumberMethods *tp_as_number; - PySequenceMethods *tp_as_sequence; - PyMappingMethods *tp_as_mapping; - - /* More standard operations (here for binary compatibility) */ - - hashfunc tp_hash; - ternaryfunc tp_call; - reprfunc tp_str; - getattrofunc tp_getattro; - setattrofunc tp_setattro; - - /* Functions to access object as input/output buffer */ - PyBufferProcs *tp_as_buffer; - - /* Flags to define presence of optional/expanded features */ - long tp_flags; - - const char *tp_doc; /* Documentation string */ - - /* Assigned meaning in release 2.0 */ - /* call function for all accessible objects */ - traverseproc tp_traverse; - - /* delete references to contained objects */ - inquiry tp_clear; - - /* Assigned meaning in release 2.1 */ - /* rich comparisons */ - richcmpfunc tp_richcompare; - - /* weak reference enabler */ - Py_ssize_t tp_weaklistoffset; - - /* Iterators */ - getiterfunc tp_iter; - iternextfunc tp_iternext; - - /* Attribute descriptor and subclassing stuff */ - struct PyMethodDef *tp_methods; - struct PyMemberDef *tp_members; - struct PyGetSetDef *tp_getset; - struct _typeobject *tp_base; - PyObject *tp_dict; - descrgetfunc tp_descr_get; - descrsetfunc tp_descr_set; - Py_ssize_t tp_dictoffset; - initproc tp_init; - allocfunc tp_alloc; - newfunc tp_new; - freefunc tp_free; /* Low-level free-memory routine */ - inquiry tp_is_gc; /* For PyObject_IS_GC */ - PyObject *tp_bases; - PyObject *tp_mro; /* method resolution order */ - PyObject *tp_cache; - PyObject *tp_subclasses; - PyObject *tp_weaklist; - destructor tp_del; - - /* Type attribute cache version tag. Added in version 2.6 */ - unsigned int tp_version_tag; - - destructor tp_finalize; -} PyTypeObject; - -typedef struct { - PyTypeObject ht_type; - PyNumberMethods as_number; - PyMappingMethods as_mapping; - PySequenceMethods as_sequence; - PyBufferProcs as_buffer; - PyObject *ht_name, *ht_slots; -} PyHeapTypeObject; +#include <cpyext_typeobject.h> #define PyObject_Bytes PyObject_Str diff --git a/pypy/module/cpyext/include/structmember.h b/pypy/module/cpyext/include/structmember.h --- a/pypy/module/cpyext/include/structmember.h +++ b/pypy/module/cpyext/include/structmember.h @@ -19,22 +19,6 @@ #define offsetof(type, member) ( (int) & ((type*)0) -> member ) #endif -/* An array of memberlist structures defines the name, type and offset - of selected members of a C structure. These can be read by - PyMember_Get() and set by PyMember_Set() (except if their READONLY flag - is set). The array must be terminated with an entry whose name - pointer is NULL. */ - - - -typedef struct PyMemberDef { - /* Current version, use this */ - char *name; - int type; - Py_ssize_t offset; - int flags; - char *doc; -} PyMemberDef; /* Types */ #define T_SHORT 0 diff --git a/pypy/module/cpyext/methodobject.py b/pypy/module/cpyext/methodobject.py --- a/pypy/module/cpyext/methodobject.py +++ b/pypy/module/cpyext/methodobject.py @@ -11,23 +11,13 @@ CONST_STRING, METH_CLASS, METH_COEXIST, METH_KEYWORDS, METH_NOARGS, METH_O, METH_STATIC, METH_VARARGS, PyObject, PyObjectFields, bootstrap_function, build_type_checkers, cpython_api, cpython_struct, generic_cpy_call, - PyTypeObjectPtr, slot_function) + PyTypeObjectPtr, slot_function, cts, api_decl) from pypy.module.cpyext.pyobject import ( Py_DecRef, from_ref, make_ref, as_pyobj, make_typedescr) -PyCFunction_typedef = rffi.COpaquePtr(typedef='PyCFunction') -PyCFunction = lltype.Ptr(lltype.FuncType([PyObject, PyObject], PyObject)) -PyCFunctionKwArgs = lltype.Ptr(lltype.FuncType([PyObject, PyObject, PyObject], - PyObject)) - -PyMethodDef = cpython_struct( - 'PyMethodDef', - [('ml_name', rffi.CONST_CCHARP), - ('ml_meth', PyCFunction_typedef), - ('ml_flags', rffi.INT_real), - ('ml_doc', rffi.CONST_CCHARP), - ]) - +PyMethodDef = cts.gettype('PyMethodDef') +PyCFunction = cts.gettype('PyCFunction') +PyCFunctionKwArgs = cts.gettype('PyCFunctionWithKeywords') PyCFunctionObjectStruct = cpython_struct( 'PyCFunctionObject', PyObjectFields + ( @@ -77,7 +67,7 @@ raise oefmt(space.w_TypeError, "%s() takes no keyword arguments", self.name) - func = rffi.cast(PyCFunction, self.ml.c_ml_meth) + func = self.ml.c_ml_meth length = space.int_w(space.len(w_args)) if flags & METH_KEYWORDS: func = rffi.cast(PyCFunctionKwArgs, self.ml.c_ml_meth) @@ -282,7 +272,7 @@ def PyCFunction_NewEx(space, ml, w_self, w_name): return space.wrap(W_PyCFunctionObject(space, ml, w_self, w_name)) -@cpython_api([PyObject], PyCFunction_typedef) +@api_decl("PyCFunction PyCFunction_GetFunction(PyObject *)", cts) def PyCFunction_GetFunction(space, w_obj): try: cfunction = space.interp_w(W_PyCFunctionObject, w_obj) @@ -337,4 +327,3 @@ if name == "__methods__": return space.newlist(method_list_w) raise OperationError(space.w_AttributeError, space.wrap(name)) - diff --git a/pypy/module/cpyext/parse/cpyext_object.h b/pypy/module/cpyext/parse/cpyext_object.h new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/parse/cpyext_object.h @@ -0,0 +1,282 @@ + +typedef ssize_t Py_ssize_t; + +#define PyObject_HEAD \ + Py_ssize_t ob_refcnt; \ + Py_ssize_t ob_pypy_link; \ + struct _typeobject *ob_type; + +#define PyObject_VAR_HEAD \ + PyObject_HEAD \ + Py_ssize_t ob_size; /* Number of items in variable part */ + +typedef struct _object { + PyObject_HEAD +} PyObject; + +typedef struct { + PyObject_VAR_HEAD +} PyVarObject; + +struct _typeobject; +typedef void (*freefunc)(void *); +typedef void (*destructor)(PyObject *); +typedef int (*printfunc)(PyObject *, FILE *, int); +typedef PyObject *(*getattrfunc)(PyObject *, char *); +typedef PyObject *(*getattrofunc)(PyObject *, PyObject *); +typedef int (*setattrfunc)(PyObject *, char *, PyObject *); +typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *); +typedef int (*cmpfunc)(PyObject *, PyObject *); +typedef PyObject *(*reprfunc)(PyObject *); +typedef long (*hashfunc)(PyObject *); +typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int); +typedef PyObject *(*getiterfunc) (PyObject *); +typedef PyObject *(*iternextfunc) (PyObject *); +typedef PyObject *(*descrgetfunc) (PyObject *, PyObject *, PyObject *); +typedef int (*descrsetfunc) (PyObject *, PyObject *, PyObject *); +typedef int (*initproc)(PyObject *, PyObject *, PyObject *); +typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *); +typedef PyObject *(*allocfunc)(struct _typeobject *, Py_ssize_t); + +typedef PyObject * (*unaryfunc)(PyObject *); +typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); +typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *); +typedef int (*inquiry)(PyObject *); +typedef Py_ssize_t (*lenfunc)(PyObject *); +typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); +typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); +typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); +typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); +typedef int(*objobjargproc)(PyObject *, PyObject *, PyObject *); + + +/* Py3k buffer interface, adapted for PyPy */ +#define Py_MAX_NDIMS 32 +#define Py_MAX_FMT 128 +typedef struct bufferinfo { + void *buf; + PyObject *obj; /* owned reference */ + Py_ssize_t len; + Py_ssize_t itemsize; /* This is Py_ssize_t so it can be + pointed to by strides in simple case.*/ + int readonly; + int ndim; + char *format; + Py_ssize_t *shape; + Py_ssize_t *strides; + Py_ssize_t *suboffsets; /* alway NULL for app-level objects*/ + unsigned char _format[Py_MAX_FMT]; + Py_ssize_t _strides[Py_MAX_NDIMS]; + Py_ssize_t _shape[Py_MAX_NDIMS]; + /* static store for shape and strides of + mono-dimensional buffers. */ + /* Py_ssize_t smalltable[2]; */ + void *internal; /* always NULL for app-level objects */ +} Py_buffer; + +typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); +typedef void (*releasebufferproc)(PyObject *, Py_buffer *); +/* end Py3k buffer interface */ + +typedef int (*objobjproc)(PyObject *, PyObject *); +typedef int (*visitproc)(PyObject *, void *); +typedef int (*traverseproc)(PyObject *, visitproc, void *); + + +typedef struct { + /* Number implementations must check *both* + arguments for proper type and implement the necessary conversions + in the slot functions themselves. */ + + binaryfunc nb_add; + binaryfunc nb_subtract; + binaryfunc nb_multiply; + binaryfunc nb_remainder; + binaryfunc nb_divmod; + ternaryfunc nb_power; + unaryfunc nb_negative; + unaryfunc nb_positive; + unaryfunc nb_absolute; + inquiry nb_bool; + unaryfunc nb_invert; + binaryfunc nb_lshift; + binaryfunc nb_rshift; + binaryfunc nb_and; + binaryfunc nb_xor; + binaryfunc nb_or; + unaryfunc nb_int; + void *nb_reserved; /* the slot formerly known as nb_long */ + unaryfunc nb_float; + + binaryfunc nb_inplace_add; + binaryfunc nb_inplace_subtract; + binaryfunc nb_inplace_multiply; + binaryfunc nb_inplace_remainder; + ternaryfunc nb_inplace_power; + binaryfunc nb_inplace_lshift; + binaryfunc nb_inplace_rshift; + binaryfunc nb_inplace_and; + binaryfunc nb_inplace_xor; + binaryfunc nb_inplace_or; + + binaryfunc nb_floor_divide; + binaryfunc nb_true_divide; + binaryfunc nb_inplace_floor_divide; + binaryfunc nb_inplace_true_divide; + + unaryfunc nb_index; + + binaryfunc nb_matrix_multiply; + binaryfunc nb_inplace_matrix_multiply; +} PyNumberMethods; + +typedef struct { + lenfunc sq_length; + binaryfunc sq_concat; + ssizeargfunc sq_repeat; + ssizeargfunc sq_item; + void *was_sq_slice; + ssizeobjargproc sq_ass_item; + void *was_sq_ass_slice; + objobjproc sq_contains; + + binaryfunc sq_inplace_concat; + ssizeargfunc sq_inplace_repeat; +} PySequenceMethods; + +typedef struct { + lenfunc mp_length; + binaryfunc mp_subscript; + objobjargproc mp_ass_subscript; +} PyMappingMethods; + +typedef struct { + unaryfunc am_await; + unaryfunc am_aiter; + unaryfunc am_anext; +} PyAsyncMethods; + +typedef struct { + getbufferproc bf_getbuffer; + releasebufferproc bf_releasebuffer; +} PyBufferProcs; + +/* from descrobject.h */ +typedef PyObject *(*getter)(PyObject *, void *); +typedef int (*setter)(PyObject *, PyObject *, void *); + +typedef struct PyGetSetDef { + char *name; + getter get; + setter set; + char *doc; + void *closure; +} PyGetSetDef; + +/* from methodobject.h */ +typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); +typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, + PyObject *); +typedef PyObject *(*PyNoArgsFunction)(PyObject *); + +struct PyMethodDef { + const char *ml_name; /* The name of the built-in function/method */ + PyCFunction ml_meth; /* The C function that implements it */ + int ml_flags; /* Combination of METH_xxx flags, which mostly + describe the args expected by the C func */ + const char *ml_doc; /* The __doc__ attribute, or NULL */ +}; +typedef struct PyMethodDef PyMethodDef; + +/* from structmember.h */ +typedef struct PyMemberDef { + /* Current version, use this */ + char *name; + int type; + Py_ssize_t offset; + int flags; + char *doc; +} PyMemberDef; + + +typedef struct _typeobject { + PyObject_VAR_HEAD + const char *tp_name; /* For printing, in format "<module>.<name>" */ + Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ + + /* Methods to implement standard operations */ + + destructor tp_dealloc; + printfunc tp_print; + getattrfunc tp_getattr; + setattrfunc tp_setattr; + PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) + or tp_reserved (Python 3) */ + reprfunc tp_repr; + + /* Method suites for standard classes */ + + PyNumberMethods *tp_as_number; + PySequenceMethods *tp_as_sequence; + PyMappingMethods *tp_as_mapping; + + /* More standard operations (here for binary compatibility) */ + + hashfunc tp_hash; + ternaryfunc tp_call; + reprfunc tp_str; + getattrofunc tp_getattro; + setattrofunc tp_setattro; + + /* Functions to access object as input/output buffer */ + PyBufferProcs *tp_as_buffer; + + /* Flags to define presence of optional/expanded features */ + long tp_flags; + + const char *tp_doc; /* Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + traverseproc tp_traverse; + + /* delete references to contained objects */ + inquiry tp_clear; + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + richcmpfunc tp_richcompare; + + /* weak reference enabler */ + Py_ssize_t tp_weaklistoffset; + + /* Iterators */ + getiterfunc tp_iter; + iternextfunc tp_iternext; + + /* Attribute descriptor and subclassing stuff */ + struct PyMethodDef *tp_methods; + struct PyMemberDef *tp_members; + struct PyGetSetDef *tp_getset; + struct _typeobject *tp_base; + PyObject *tp_dict; + descrgetfunc tp_descr_get; + descrsetfunc tp_descr_set; + Py_ssize_t tp_dictoffset; + initproc tp_init; + allocfunc tp_alloc; + newfunc tp_new; + freefunc tp_free; /* Low-level free-memory routine */ + inquiry tp_is_gc; /* For PyObject_IS_GC */ + PyObject *tp_bases; + PyObject *tp_mro; /* method resolution order */ + PyObject *tp_cache; + PyObject *tp_subclasses; + PyObject *tp_weaklist; + destructor tp_del; + + /* Type attribute cache version tag. Added in version 2.6 */ + unsigned int tp_version_tag; + + destructor tp_finalize; +} PyTypeObject; diff --git a/pypy/module/cpyext/parse/cpyext_typeobject.h b/pypy/module/cpyext/parse/cpyext_typeobject.h new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/parse/cpyext_typeobject.h @@ -0,0 +1,9 @@ +typedef struct { + PyTypeObject ht_type; + PyNumberMethods as_number; + PyMappingMethods as_mapping; + PySequenceMethods as_sequence; + PyBufferProcs as_buffer; + PyObject *ht_name, *ht_slots; +} PyHeapTypeObject; + diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -7,9 +7,9 @@ from rpython.rlib import rgc # Force registration of gc.collect from pypy.module.cpyext.api import ( slot_function, generic_cpy_call, PyObject, Py_ssize_t, - pypy_decl, Py_buffer, Py_bufferP) + pypy_decl, Py_buffer, Py_bufferP, PyTypeObjectPtr) from pypy.module.cpyext.typeobjectdefs import ( - unaryfunc, ternaryfunc, PyTypeObjectPtr, binaryfunc, + unaryfunc, ternaryfunc, binaryfunc, getattrfunc, getattrofunc, setattrofunc, lenfunc, ssizeargfunc, inquiry, ssizessizeargfunc, ssizeobjargproc, iternextfunc, initproc, richcmpfunc, cmpfunc, hashfunc, descrgetfunc, descrsetfunc, objobjproc, objobjargproc, diff --git a/pypy/module/cpyext/test/test_api.py b/pypy/module/cpyext/test/test_api.py --- a/pypy/module/cpyext/test/test_api.py +++ b/pypy/module/cpyext/test/test_api.py @@ -5,7 +5,7 @@ from pypy.module.cpyext.state import State from pypy.module.cpyext.api import ( slot_function, cpython_api, copy_header_files, INTERPLEVEL_API, - Py_ssize_t, Py_ssize_tP, PyObject) + Py_ssize_t, Py_ssize_tP, PyObject, cts) from pypy.module.cpyext.test.test_cpyext import freeze_refcnts, LeakCheckingTest from pypy.interpreter.error import OperationError from rpython.rlib import rawrefcount @@ -99,10 +99,10 @@ def test_typedef(self, space): from rpython.translator.c.database import LowLevelDatabase db = LowLevelDatabase() - assert PyPy_TypedefTest1.api_func.get_c_restype(db) == 'Py_ssize_t' - assert PyPy_TypedefTest1.api_func.get_c_args(db) == 'Py_ssize_t arg0' - assert PyPy_TypedefTest2.api_func.get_c_restype(db) == 'Py_ssize_t *' - assert PyPy_TypedefTest2.api_func.get_c_args(db) == 'Py_ssize_t *arg0' + assert PyPy_TypedefTest1.api_func.get_c_restype(db) == 'Signed' + assert PyPy_TypedefTest1.api_func.get_c_args(db) == 'Signed arg0' + assert PyPy_TypedefTest2.api_func.get_c_restype(db) == 'Signed *' + assert PyPy_TypedefTest2.api_func.get_c_args(db) == 'Signed *arg0' PyPy_TypedefTest1(space, 0) ppos = lltype.malloc(Py_ssize_tP.TO, 1, flavor='raw') @@ -113,7 +113,7 @@ @pytest.mark.skipif(os.environ.get('USER')=='root', reason='root can write to all files') def test_copy_header_files(tmpdir): - copy_header_files(tmpdir, True) + copy_header_files(cts, tmpdir, True) def check(name): f = tmpdir.join(name) assert f.check(file=True) diff --git a/pypy/module/cpyext/test/test_bytesobject.py b/pypy/module/cpyext/test/test_bytesobject.py --- a/pypy/module/cpyext/test/test_bytesobject.py +++ b/pypy/module/cpyext/test/test_bytesobject.py @@ -7,8 +7,7 @@ from pypy.module.cpyext.bytesobject import new_empty_str, PyBytesObject from pypy.module.cpyext.api import PyObjectP, PyObject, Py_ssize_tP, generic_cpy_call, Py_buffer from pypy.module.cpyext.pyobject import Py_DecRef, from_ref, make_ref, as_pyobj -from pypy.module.cpyext.typeobjectdefs import PyTypeObjectPtr - +from pypy.module.cpyext.api import PyTypeObjectPtr class AppTestBytesObject(AppTestCpythonExtensionBase): diff --git a/pypy/module/cpyext/test/test_cparser.py b/pypy/module/cpyext/test/test_cparser.py new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/test/test_cparser.py @@ -0,0 +1,218 @@ +from rpython.flowspace.model import const +from rpython.flowspace.objspace import build_flow +from rpython.translator.simplify import simplify_graph +from rpython.rtyper.lltypesystem import rffi, lltype +from pypy.module.cpyext.cparser import parse_source, CTypeSpace + +def test_configure(): + decl = """ + typedef ssize_t Py_ssize_t; + + typedef struct { + Py_ssize_t ob_refcnt; + Py_ssize_t ob_pypy_link; + double ob_fval; + } TestFloatObject; + """ + cts = parse_source(decl) + TestFloatObject = cts.definitions['TestFloatObject'] + assert isinstance(TestFloatObject, lltype.Struct) + assert TestFloatObject.c_ob_refcnt == rffi.SSIZE_T + assert TestFloatObject.c_ob_pypy_link == rffi.SSIZE_T + assert TestFloatObject.c_ob_fval == rffi.DOUBLE + +def test_simple(): + decl = "typedef ssize_t Py_ssize_t;" + cts = parse_source(decl) + assert cts.definitions == {'Py_ssize_t': rffi.SSIZE_T} + +def test_macro(): + decl = """ + typedef ssize_t Py_ssize_t; + + #define PyObject_HEAD \ + Py_ssize_t ob_refcnt; \ + Py_ssize_t ob_pypy_link; \ + + typedef struct { + PyObject_HEAD + double ob_fval; + } PyFloatObject; + """ + cts = parse_source(decl) + assert 'PyFloatObject' in cts.definitions + assert 'PyObject_HEAD' in cts.macros + +def test_include(): + cdef1 = """ + typedef ssize_t Py_ssize_t; + + #define PyObject_HEAD \ + Py_ssize_t ob_refcnt; \ + Py_ssize_t ob_pypy_link; \ + + typedef struct { _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit