Author: Ronan Lamy <ronan.l...@gmail.com> Branch: hpy Changeset: r98059:19d6e8fbace2 Date: 2019-11-15 20:57 +0100 http://bitbucket.org/pypy/pypy/changeset/19d6e8fbace2/
Log: (antocuni, ronan) Setup the infrastructure to compile and test HPy modules Vendor files from pyhandle/hpy inside pypy/module/hpy_universal/test/_vendored with some temporary changes. diff --git a/pypy/module/hpy_universal/test/__init__.py b/pypy/module/hpy_universal/test/__init__.py new file mode 100644 diff --git a/pypy/module/hpy_universal/test/_vendored/__init__.py b/pypy/module/hpy_universal/test/_vendored/__init__.py new file mode 100644 diff --git a/pypy/module/hpy_universal/test/_vendored/include/cpython/hpy.h b/pypy/module/hpy_universal/test/_vendored/include/cpython/hpy.h new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/include/cpython/hpy.h @@ -0,0 +1,142 @@ +#ifndef HPy_CPYTHON_H +#define HPy_CPYTHON_H + + + + +/* XXX: it would be nice if we could include hpy.h WITHOUT bringing in all the + stuff from Python.h, to make sure that people don't use the CPython API by + mistake. How to achieve it, though? */ + +/* XXX: should we: + * - enforce PY_SSIZE_T_CLEAN in hpy + * - make it optional + * - make it the default but give a way to disable it? + */ +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#ifdef __GNUC__ +#define HPyAPI_FUNC(restype) __attribute__((unused)) static inline restype +#else +#define HPyAPI_FUNC(restype) static inline restype +#endif + + +typedef struct { PyObject *_o; } HPy; +typedef long HPyContext; + +HPyAPI_FUNC(HPyContext) +_HPyGetContext(void) { + return 42; +} + +/* For internal usage only. These should be #undef at the end of this header. + If you need to convert HPy to PyObject* and vice-versa, you should use the + official way to do it (not implemented yet :) +*/ +#define _h2py(x) (x._o) +#define _py2h(o) ((HPy){o}) + + +#define HPy_NULL ((HPy){NULL}) +#define HPy_IsNull(x) ((x)._o == NULL) + +HPyAPI_FUNC(HPy) +HPyNone_Get(HPyContext ctx) +{ + Py_INCREF(Py_None); + return _py2h(Py_None); +} + +HPyAPI_FUNC(HPy) +HPy_Dup(HPyContext ctx, HPy handle) +{ + Py_XINCREF(_h2py(handle)); + return handle; +} + +HPyAPI_FUNC(void) +HPy_Close(HPyContext ctx, HPy handle) +{ + Py_XDECREF(_h2py(handle)); +} + +/* moduleobject.h */ +typedef PyModuleDef HPyModuleDef; +#define HPyModuleDef_HEAD_INIT PyModuleDef_HEAD_INIT + +HPyAPI_FUNC(HPy) +HPyModule_Create(HPyContext ctx, HPyModuleDef *mdef) { + return _py2h(PyModule_Create(mdef)); +} + +#define HPy_MODINIT(modname) \ + static HPy init_##modname##_impl(HPyContext ctx); \ + PyMODINIT_FUNC \ + PyInit_##modname(void) \ + { \ + return _h2py(init_##modname##_impl(_HPyGetContext())); \ + } + +/* methodobject.h */ +typedef PyMethodDef HPyMethodDef; + + +/* function declaration */ + +#define HPy_FUNCTION(NAME) \ + static HPy NAME##_impl(HPyContext, HPy, HPy); \ + static PyObject* NAME(PyObject *self, PyObject *args) \ + { \ + return _h2py(NAME##_impl(_HPyGetContext(), _py2h(self), _py2h(args)));\ + } + + +HPyAPI_FUNC(int) +HPyArg_ParseTuple(HPyContext ctx, HPy args, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + int res = PyArg_VaParse(_h2py(args), fmt, vl); + va_end(vl); + /* XXX incref all returned 'PyObject*' */ + return res; +} + + +HPyAPI_FUNC(HPy) +HPyLong_FromLong(HPyContext ctx, long v) +{ + return _py2h(PyLong_FromLong(v)); +} + +HPyAPI_FUNC(HPy) +HPyNumber_Add(HPyContext ctx, HPy x, HPy y) +{ + return _py2h(PyNumber_Add(_h2py(x), _h2py(y))); +} + +HPyAPI_FUNC(HPy) +HPyUnicode_FromString(HPyContext ctx, const char *utf8) +{ + return _py2h(PyUnicode_FromString(utf8)); +} + +HPyAPI_FUNC(HPy) +HPy_FromPyObject(HPyContext ctx, PyObject *obj) +{ + Py_XINCREF(obj); + return _py2h(obj); +} + +HPyAPI_FUNC(PyObject *) +HPy_AsPyObject(HPyContext ctx, HPy h) +{ + PyObject *result = _h2py(h); + Py_XINCREF(result); + return result; +} + + +#endif /* !HPy_CPYTHON_H */ diff --git a/pypy/module/hpy_universal/test/_vendored/include/hpy.h b/pypy/module/hpy_universal/test/_vendored/include/hpy.h new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/include/hpy.h @@ -0,0 +1,10 @@ +#ifndef HPy_H +#define HPy_H + +#ifdef HPY_UNIVERSAL_ABI +# include "universal/hpy.h" +#else +# include "cpython/hpy.h" +#endif + +#endif /* HPy_H */ diff --git a/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_ctx.h b/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_ctx.h new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_ctx.h @@ -0,0 +1,24 @@ + +/* + DO NOT EDIT THIS FILE! + + This file is automatically generated by tools/autogen.py from tools/public_api.h. + Run this to regenerate: + make autogen + +*/ + +struct _HPyContext_s { + int ctx_version; + HPy (*ctx_Module_Create)(HPyContext ctx, HPyModuleDef *def); + HPy (*ctx_None_Get)(HPyContext ctx); + HPy (*ctx_Dup)(HPyContext ctx, HPy h); + void (*ctx_Close)(HPyContext ctx, HPy h); + HPy (*ctx_Long_FromLong)(HPyContext ctx, long value); + int (*ctx_Arg_ParseTuple)(HPyContext ctx, HPy args, const char *fmt, va_list _vl); + HPy (*ctx_Number_Add)(HPyContext ctx, HPy x, HPy y); + HPy (*ctx_Unicode_FromString)(HPyContext ctx, const char *utf8); + HPy (*ctx_FromPyObject)(HPyContext ctx, struct _object *obj); + struct _object *(*ctx_AsPyObject)(HPyContext ctx, HPy h); + struct _object *(*ctx_CallRealFunctionFromTrampoline)(HPyContext ctx, struct _object *self, struct _object *args, HPyCFunction func); +}; diff --git a/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_func.h b/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_func.h new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/include/universal/autogen_func.h @@ -0,0 +1,54 @@ + +/* + DO NOT EDIT THIS FILE! + + This file is automatically generated by tools/autogen.py from tools/public_api.h. + Run this to regenerate: + make autogen + +*/ + +static inline HPy HPyModule_Create(HPyContext ctx, HPyModuleDef *def) { + return ctx->ctx_Module_Create ( ctx, def ); +} + +static inline HPy HPyNone_Get(HPyContext ctx) { + return ctx->ctx_None_Get ( ctx ); +} + +static inline HPy HPy_Dup(HPyContext ctx, HPy h) { + return ctx->ctx_Dup ( ctx, h ); +} + +static inline void HPy_Close(HPyContext ctx, HPy h) { + ctx->ctx_Close ( ctx, h ); +} + +static inline HPy HPyLong_FromLong(HPyContext ctx, long value) { + return ctx->ctx_Long_FromLong ( ctx, value ); +} + +static inline int HPyArg_ParseTuple(HPyContext ctx, HPy args, const char *fmt, ...) { + va_list _vl; va_start(_vl, fmt); int _res = ctx->ctx_Arg_ParseTuple ( ctx, args, fmt, _vl ); va_end(_vl); return _res; +} + +static inline HPy HPyNumber_Add(HPyContext ctx, HPy x, HPy y) { + return ctx->ctx_Number_Add ( ctx, x, y ); +} + +static inline HPy HPyUnicode_FromString(HPyContext ctx, const char *utf8) { + return ctx->ctx_Unicode_FromString ( ctx, utf8 ); +} + +static inline HPy HPy_FromPyObject(HPyContext ctx, struct _object *obj) { + return ctx->ctx_FromPyObject ( ctx, obj ); +} + +static inline struct _object *HPy_AsPyObject(HPyContext ctx, HPy h) { + return ctx->ctx_AsPyObject ( ctx, h ); +} + +static inline struct _object *_HPy_CallRealFunctionFromTrampoline(HPyContext ctx, struct _object *self, struct _object *args, HPyCFunction func) { + return ctx->ctx_CallRealFunctionFromTrampoline ( ctx, self, args, func ); +} + diff --git a/pypy/module/hpy_universal/test/_vendored/include/universal/hpy.h b/pypy/module/hpy_universal/test/_vendored/include/universal/hpy.h new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/include/universal/hpy.h @@ -0,0 +1,79 @@ +#ifndef HPy_UNIVERSAL_H +#define HPy_UNIVERSAL_H + +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> + +typedef intptr_t HPy_ssize_t; +typedef struct { HPy_ssize_t _i; } HPy; + +typedef struct _HPyContext_s *HPyContext; +typedef HPy (*HPyCFunction)(HPyContext, HPy self, HPy args); +struct _object; /* that's PyObject inside CPython */ +typedef struct _object *(*_HPy_CPyCFunction)(struct _object *self, + struct _object *args); + +#define HPy_NULL ((HPy){0}) +#define HPy_IsNull(x) ((x)._i == 0) + +typedef void (*_HPyMethodPairFunc)(HPyCFunction *out_func, + _HPy_CPyCFunction *out_trampoline); + +typedef struct { + const char *ml_name; /* The name of the built-in function/method */ + _HPyMethodPairFunc ml_meth; /* see HPy_FUNCTION() */ + 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 */ +} HPyMethodDef; + + +#define HPyModuleDef_HEAD_INIT NULL + +typedef struct { + void *dummy; // this is needed because we put a comma after HPyModuleDef_HEAD_INIT :( + const char* m_name; + const char* m_doc; + HPy_ssize_t m_size; + HPyMethodDef *m_methods; +} HPyModuleDef; + +#define HPy_MODINIT(modname) \ + HPyContext _ctx_for_trampolines; \ + static HPy init_##modname##_impl(HPyContext ctx); \ + HPy HPyInit_##modname(HPyContext ctx) \ + { \ + _ctx_for_trampolines = ctx; \ + return init_##modname##_impl(ctx); \ + } + +#include "autogen_ctx.h" +#include "autogen_func.h" + +extern HPyContext _ctx_for_trampolines; + + +#define HPy_FUNCTION(fnname) \ + static HPy fnname##_impl(HPyContext ctx, HPy self, HPy args); \ + static struct _object * \ + fnname##_trampoline(struct _object *self, struct _object *args) \ + { \ + return _HPy_CallRealFunctionFromTrampoline( \ + _ctx_for_trampolines, self, args, fnname##_impl); \ + } \ + static void \ + fnname(HPyCFunction *out_func, _HPy_CPyCFunction *out_trampoline) \ + { \ + *out_func = fnname##_impl; \ + *out_trampoline = fnname##_trampoline; \ + } + +#define METH_VARARGS 0x0001 +#define METH_KEYWORDS 0x0002 +/* METH_NOARGS and METH_O must not be combined with the flags above. */ +#define METH_NOARGS 0x0004 +#define METH_O 0x0008 + + +#endif /* HPy_UNIVERSAL_H */ diff --git a/pypy/module/hpy_universal/test/_vendored/support.py b/pypy/module/hpy_universal/test/_vendored/support.py new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/_vendored/support.py @@ -0,0 +1,183 @@ +import os, sys +import pytest +import re +#import importlib.util +#from importlib.machinery import ExtensionFileLoader + +THIS_DIR = os.path.dirname(__file__) +INCLUDE_DIR = os.path.join(THIS_DIR, '../hpy-api/include') + + +r_marker_init = re.compile(r"\s*@INIT\s*$") +r_marker_export = re.compile(r"\s*@EXPORT\s+(\w+)\s+(METH_\w+)\s*$") + +INIT_TEMPLATE = """ +static HPyMethodDef MyTestMethods[] = { + %(methods)s + {NULL, NULL, 0, NULL} +}; + +static HPyModuleDef moduledef = { + HPyModuleDef_HEAD_INIT, + .m_name = "%(name)s", + .m_doc = "some test for hpy", + .m_size = -1, + .m_methods = MyTestMethods +}; + +HPy_MODINIT(%(name)s) +static HPy init_%(name)s_impl(HPyContext ctx) +{ + HPy m; + m = HPyModule_Create(ctx, &moduledef); + if (HPy_IsNull(m)) + return HPy_NULL; + return m; +} +""" + + +def expand_template(source_template, name): + method_table = [] + expanded_lines = ['#include <hpy.h>'] + for line in source_template.split('\n'): + match = r_marker_init.match(line) + if match: + exp = INIT_TEMPLATE % { + 'methods': '\n '.join(method_table), + 'name': name} + method_table = None # don't fill it any more + expanded_lines.append(exp) + continue + + match = r_marker_export.match(line) + if match: + ml_name, ml_flags = match.group(1), match.group(2) + method_table.append('{"%s", %s, %s, NULL},' % ( + ml_name, ml_name, ml_flags)) + continue + + expanded_lines.append(line) + return '\n'.join(expanded_lines) + + +#class HPyLoader(ExtensionFileLoader): +# def create_module(self, spec): +# import hpy_universal +# return hpy_universal.load(spec.origin, "HPyInit_" + spec.name) + +class ExtensionCompiler: + def __init__(self, tmpdir, abimode): + self.tmpdir = tmpdir + self.abimode = abimode + + def make_module(self, source_template, name): + universal_mode = self.abimode == 'universal' + source = expand_template(source_template, name) + filename = self.tmpdir.join(name + '.c') + filename.write(source) + # + ext = get_extension(str(filename), name, include_dirs=[INCLUDE_DIR], + extra_compile_args=['-Wfatal-errors']) + so_filename = c_compile(str(self.tmpdir), ext, compiler_verbose=False, + universal_mode=universal_mode) + # + if universal_mode: + loader = HPyLoader(name, so_filename) + spec = importlib.util.spec_from_loader(name, loader) + else: + spec = importlib.util.spec_from_file_location(name, so_filename) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + +@pytest.mark.usefixtures('initargs') +class HPyTest: + @pytest.fixture() + def initargs(self, compiler): + self.compiler = compiler + + def make_module(self, source_template, name='mytest'): + return self.compiler.make_module(source_template, name) + + +# the few functions below are copied and adapted from cffi/ffiplatform.py + +def get_extension(srcfilename, modname, sources=(), **kwds): + from distutils.core import Extension + allsources = [srcfilename] + for src in sources: + allsources.append(os.path.normpath(src)) + return Extension(name=modname, sources=allsources, **kwds) + +def c_compile(tmpdir, ext, compiler_verbose=0, debug=None, + universal_mode=False): + """Compile a C extension module using distutils.""" + + saved_environ = os.environ.copy() + try: + outputfilename = _build(tmpdir, ext, compiler_verbose, debug, + universal_mode) + outputfilename = os.path.abspath(outputfilename) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename + +def _build(tmpdir, ext, compiler_verbose=0, debug=None, universal_mode=False): + # XXX compact but horrible :-( + from distutils.core import Distribution + import distutils.errors, distutils.log + # + dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() + options = dist.get_option_dict('build_ext') + if debug is None: + debug = sys.flags.debug + options['debug'] = ('ffiplatform', debug) + options['force'] = ('ffiplatform', True) + options['build_lib'] = ('ffiplatform', tmpdir) + options['build_temp'] = ('ffiplatform', tmpdir) + # + old_level = distutils.log.set_threshold(0) or 0 + try: + distutils.log.set_verbosity(compiler_verbose) + if universal_mode: + cmd_obj = dist.get_command_obj('build_ext') + cmd_obj.finalize_options() + soname = _build_universal(tmpdir, ext, cmd_obj.include_dirs) + else: + dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() + finally: + distutils.log.set_threshold(old_level) + # + return soname + +def _build_universal(tmpdir, ext, cpython_include_dirs): + from distutils.ccompiler import new_compiler, get_default_compiler + from distutils.sysconfig import customize_compiler + + compiler = new_compiler(get_default_compiler()) + customize_compiler(compiler) + + include_dirs = ext.include_dirs + cpython_include_dirs + objects = compiler.compile(ext.sources, + output_dir=tmpdir, + macros=[('HPY_UNIVERSAL_ABI', None)], + include_dirs=include_dirs) + + filename = ext.name + '.hpy.so' + compiler.link(compiler.SHARED_LIBRARY, + objects, + filename, + tmpdir + # export_symbols=... + ) + return os.path.join(tmpdir, filename) diff --git a/pypy/module/hpy_universal/test/support.py b/pypy/module/hpy_universal/test/support.py new file mode 100644 --- /dev/null +++ b/pypy/module/hpy_universal/test/support.py @@ -0,0 +1,49 @@ +import py +import pytest +from pypy.interpreter.gateway import interp2app, unwrap_spec + +from rpython.tool.udir import udir +from ._vendored import support as _support + +INCLUDE_DIR = str(py.path.local(__file__).dirpath().join('_vendored/include')) + +class ExtensionCompiler(object): + def __init__(self, base_dir): + self.base_dir = base_dir + + def get_builddir(self, name='mytest'): + builddir = py.path.local.make_numbered_dir( + rootdir=py.path.local(self.base_dir), + prefix=name + '-', + keep=0) # keep everything + return builddir + + +class HPyTest(object): + def setup_class(cls): + if cls.runappdirect: + pytest.skip() + cls.compiler = ExtensionCompiler(udir) + + @unwrap_spec(source_template='text', name='text') + def descr_make_module(space, source_template, name='mytest'): + source = _support.expand_template(source_template, name) + tmpdir = cls.compiler.get_builddir() + filename = tmpdir.join(name+ '.c') + filename.write(source) + # + ext = _support.get_extension(str(filename), name, include_dirs=[INCLUDE_DIR], + extra_compile_args=['-Wfatal-errors']) + so_filename = _support.c_compile(str(tmpdir), ext, compiler_verbose=False, + universal_mode=True) + # + w_mod = space.appexec( + [space.newtext(so_filename), space.newtext(name)], + """(path, modname): + from hpy_universal import load + return load(path, 'HPyInit_' + modname) + """ + ) + return w_mod + cls.w_make_module = cls.space.wrap(interp2app(descr_make_module)) + diff --git a/pypy/module/hpy_universal/test/test_basic.py b/pypy/module/hpy_universal/test/test_basic.py --- a/pypy/module/hpy_universal/test/test_basic.py +++ b/pypy/module/hpy_universal/test/test_basic.py @@ -1,4 +1,17 @@ -class AppTestBasic(object): +from .support import HPyTest + +class AppTestBasic(HPyTest): spaceconfig = {'usemodules': ['hpy_universal']} def test_import(self): import hpy_universal + + def test_empty_module(self): + import sys + mod = self.make_module(""" + @INIT + """) + assert type(mod) is type(sys) + assert mod.__loader__.name == 'mytest' + assert mod.__spec__.loader is mod.__loader__ + assert mod.__file__ + _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit