├── test ├── __init__.py ├── debug │ └── __init__.py ├── hpy_devel │ ├── __init__.py │ └── test_abitag.py ├── test_hpyimport.py ├── test_hpyglobal.py ├── test_support.py ├── test_contextvar.py ├── test_importing.py ├── check_py27_compat.py ├── test_hpyiter.py ├── test_legacy_forbidden.py ├── test_hpytuple.py ├── test_capsule_legacy.py ├── test_eval.py ├── conftest.py └── test_hpydict.py ├── hpy ├── tools │ ├── autogen │ │ ├── testing │ │ │ └── __init__.py │ │ ├── hpyslot.py │ │ ├── __init__.py │ │ ├── autogenfile.py │ │ ├── autogen.h │ │ ├── pypy.py │ │ ├── __main__.py │ │ └── ctx.py │ ├── include_path.py │ └── valgrind │ │ └── hpy.supp ├── universal │ └── src │ │ ├── ctx_meth.h │ │ ├── ctx.c │ │ ├── api.h │ │ ├── ctx_misc.h │ │ ├── handles.h │ │ ├── misc_win32.h │ │ └── ctx_misc.c ├── trace │ ├── __init__.py │ └── src │ │ ├── include │ │ └── hpy_trace.h │ │ └── trace_internal.h ├── devel │ ├── include │ │ └── hpy │ │ │ ├── runtime │ │ │ ├── buildvalue.h │ │ │ ├── helpers.h │ │ │ ├── format.h │ │ │ ├── ctx_type.h │ │ │ ├── argparse.h │ │ │ ├── ctx_module.h │ │ │ └── structseq.h │ │ │ ├── forbid_python_h │ │ │ └── Python.h │ │ │ ├── hpyexports.h │ │ │ ├── cpy_types.h │ │ │ ├── cpython │ │ │ └── autogen_ctx.h │ │ │ ├── universal │ │ │ └── misc_trampolines.h │ │ │ └── macros.h │ ├── src │ │ └── runtime │ │ │ ├── ctx_err.c │ │ │ ├── ctx_contextvar.c │ │ │ ├── ctx_tuple.c │ │ │ ├── ctx_eval.c │ │ │ ├── ctx_bytes.c │ │ │ ├── ctx_listbuilder.c │ │ │ ├── ctx_tuplebuilder.c │ │ │ ├── ctx_capsule.c │ │ │ ├── ctx_object.c │ │ │ ├── ctx_long.c │ │ │ └── ctx_call.c │ └── abitag.py └── debug │ ├── __init__.py │ ├── src │ ├── debug_ctx_not_cpython.c │ ├── include │ │ └── hpy_debug.h │ ├── memprotect.c │ ├── stacktrace.c │ └── dhqueue.c │ ├── pytest.py │ └── leakdetector.py ├── proof-of-concept ├── requirements.txt ├── pofpackage │ ├── foo.c │ └── bar.cpp ├── setup.py ├── test_pof.py ├── pof.c └── pofcpp.cpp ├── .github ├── FUNDING.yml └── workflows │ └── valgrind-tests.yml ├── .gitattributes ├── MANIFEST.in ├── docs ├── porting-example │ └── steps │ │ ├── .gitignore │ │ ├── step_00_c_api.rst │ │ ├── step_03_hpy_final.rst │ │ ├── step_01_hpy_legacy.rst │ │ ├── step_02_hpy_legacy.rst │ │ ├── setup00.py │ │ ├── setup01.py │ │ ├── setup02.py │ │ ├── setup03.py │ │ ├── conftest.py │ │ └── test_porting_example.py ├── _static │ └── README.txt ├── _templates │ └── README.txt ├── misc │ ├── index.rst │ └── embedding.rst ├── api-reference │ ├── helpers.rst │ ├── argument-parsing.rst │ ├── hpy-field.rst │ ├── formatting.rst │ ├── hpy-global.rst │ ├── build-value.rst │ ├── hpy-call.rst │ ├── hpy-dict.rst │ ├── hpy-eval.rst │ ├── hpy-gil.rst │ ├── public-api.rst │ ├── hpy-ctx.rst │ ├── hpy-err.rst │ ├── hpy-object.rst │ ├── inline-helpers.rst │ ├── structseq.rst │ ├── hpy-sequence.rst │ ├── hpy-type.rst │ └── index.rst ├── examples │ ├── hpytype-example │ │ ├── simple_type.rst │ │ ├── setup.py │ │ ├── simple_type.c │ │ └── builtin_type.c │ ├── mixed-example │ │ ├── setup.py │ │ └── mixed.c │ ├── simple-example │ │ ├── setup.py │ │ └── simple.c │ ├── debug-example.py │ ├── trace-example.py │ ├── quickstart │ │ ├── setup.py │ │ └── quickstart.c │ └── snippets │ │ ├── legacyinit.c │ │ ├── hpyinit.c │ │ ├── setup.py │ │ ├── hpyvarargs.c │ │ └── snippets.c ├── requirements.txt ├── Makefile ├── module-state.txt ├── contributing │ └── index.rst ├── quickstart.rst ├── trace-mode.rst ├── index.rst └── xxx-unsorted-notes.txt ├── gdb-py.test ├── requirements-autogen.txt ├── microbench ├── pytest_valgrind.sh ├── pixi.toml ├── pyproject.toml ├── _valgrind_build.py ├── setup.py ├── print_other_vs_cpy.py ├── Makefile ├── README.md └── src │ └── hpy_simple.c ├── pyproject.toml ├── .readthedocs.yml ├── c_test ├── Makefile ├── test_stacktrace.c └── test_debug_handles.c ├── AUTHORS ├── LICENSE ├── .gitignore ├── CONTRIBUTING.md ├── Makefile └── README.md /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/debug/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/hpy_devel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hpy/tools/autogen/testing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /proof-of-concept/requirements.txt: -------------------------------------------------------------------------------- 1 | hpy 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: hpy 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/autogen_* linguist-generated=true 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include hpy/devel/include *.h 2 | -------------------------------------------------------------------------------- /docs/porting-example/steps/.gitignore: -------------------------------------------------------------------------------- 1 | step_03_hpy_final.py 2 | -------------------------------------------------------------------------------- /docs/_static/README.txt: -------------------------------------------------------------------------------- 1 | Static files for the Sphinx documentation. 2 | -------------------------------------------------------------------------------- /docs/_templates/README.txt: -------------------------------------------------------------------------------- 1 | Template files for the Sphinx documentation. 2 | -------------------------------------------------------------------------------- /gdb-py.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gdb --eval-command run --args python3 -m pytest -s "$@" 4 | -------------------------------------------------------------------------------- /requirements-autogen.txt: -------------------------------------------------------------------------------- 1 | pycparser==2.21 2 | py==1.11.0 3 | packaging==19.2 4 | attrs==19.3.0 5 | -------------------------------------------------------------------------------- /docs/misc/index.rst: -------------------------------------------------------------------------------- 1 | Misc Notes 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | embedding 8 | -------------------------------------------------------------------------------- /docs/api-reference/helpers.rst: -------------------------------------------------------------------------------- 1 | Misc Helpers 2 | ============ 3 | 4 | .. autocmodule:: runtime/helpers.c 5 | :members: 6 | -------------------------------------------------------------------------------- /microbench/pytest_valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | valgrind --tool=callgrind --instr-atstart=no python3-dbg -m pytest "$@" 4 | -------------------------------------------------------------------------------- /docs/api-reference/argument-parsing.rst: -------------------------------------------------------------------------------- 1 | Argument Parsing 2 | ================ 3 | 4 | .. autocmodule:: runtime/argparse.c 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-field.rst: -------------------------------------------------------------------------------- 1 | HPyField 2 | ======== 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPyField_Load,HPyField_Store 6 | -------------------------------------------------------------------------------- /docs/api-reference/formatting.rst: -------------------------------------------------------------------------------- 1 | String Formatting Helpers 2 | ========================= 3 | 4 | .. autocmodule:: runtime/format.c 5 | :no-members: 6 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-global.rst: -------------------------------------------------------------------------------- 1 | HPyGlobal 2 | ========= 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPyGlobal_Store,HPyGlobal_Load 6 | -------------------------------------------------------------------------------- /docs/api-reference/build-value.rst: -------------------------------------------------------------------------------- 1 | Building Complex Python Objects 2 | =============================== 3 | 4 | .. autocmodule:: runtime/buildvalue.c 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-call.rst: -------------------------------------------------------------------------------- 1 | HPy Call API 2 | ============ 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPy_Call,HPy_CallMethod,HPy_CallTupleDict 6 | -------------------------------------------------------------------------------- /hpy/tools/include_path.py: -------------------------------------------------------------------------------- 1 | """Prints the include path for the current Python interpreter.""" 2 | 3 | from sysconfig import get_paths as gp 4 | print(gp()['include']) 5 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-dict.rst: -------------------------------------------------------------------------------- 1 | HPy Dict 2 | ======== 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPyDict_Check, HPyDict_New, HPyDict_Keys, HPyDict_Copy 6 | -------------------------------------------------------------------------------- /docs/examples/hpytype-example/simple_type.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | simple_type.c 4 | ============= 5 | 6 | .. literalinclude:: ./simple_type.c 7 | :language: c 8 | :linenos: 9 | -------------------------------------------------------------------------------- /docs/porting-example/steps/step_00_c_api.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | step_00_c_api.c 4 | =============== 5 | 6 | .. literalinclude:: ./step_00_c_api.c 7 | :language: c 8 | :linenos: 9 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ "setuptools>=64.0", "setuptools-scm[toml]>=6.0", "wheel>=0.34.2",] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools_scm] 6 | -------------------------------------------------------------------------------- /docs/porting-example/steps/step_03_hpy_final.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | step_03_hpy_final.c 4 | =================== 5 | 6 | .. literalinclude:: ./step_03_hpy_final.c 7 | :language: c 8 | :linenos: 9 | -------------------------------------------------------------------------------- /docs/porting-example/steps/step_01_hpy_legacy.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | step_01_hpy_legacy.c 4 | ==================== 5 | 6 | .. literalinclude:: ./step_01_hpy_legacy.c 7 | :language: c 8 | :linenos: 9 | -------------------------------------------------------------------------------- /docs/porting-example/steps/step_02_hpy_legacy.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | step_02_hpy_legacy.c 4 | ==================== 5 | 6 | .. literalinclude:: ./step_02_hpy_legacy.c 7 | :language: c 8 | :linenos: 9 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-eval.rst: -------------------------------------------------------------------------------- 1 | HPy Eval 2 | ======== 3 | 4 | .. autocmodule:: hpy.h 5 | :members: HPy_SourceKind 6 | 7 | .. autocmodule:: autogen/public_api.h 8 | :members: HPy_Compile_s,HPy_EvalCode 9 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-gil.rst: -------------------------------------------------------------------------------- 1 | Leave/enter Python execution (GIL) 2 | ================================== 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPy_LeavePythonExecution,HPy_ReenterPythonExecution 6 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # readthedocs.org configuration 2 | 3 | version: 2 4 | sphinx: 5 | configuration: docs/conf.py 6 | formats: all 7 | python: 8 | version: 3.7 9 | install: 10 | - requirements: docs/requirements.txt 11 | -------------------------------------------------------------------------------- /hpy/universal/src/ctx_meth.h: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | #include "api.h" 3 | 4 | HPyAPI_IMPL void 5 | ctx_CallRealFunctionFromTrampoline(HPyContext *ctx, HPyFunc_Signature sig, 6 | HPyCFunction func, void *args); 7 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements for building the documentation 2 | Jinja2==3.1.3 3 | sphinx==5.0.2 4 | sphinx-rtd-theme==2.0.0 5 | sphinx-autobuild==2021.3.14 6 | sphinx-c-autodoc==1.3.0 7 | clang==17.0.6 8 | docutils==0.16 # docutils >= 0.17 fails to render bullet lists :/ 9 | -------------------------------------------------------------------------------- /hpy/trace/__init__.py: -------------------------------------------------------------------------------- 1 | import hpy.universal 2 | 3 | get_call_counts = hpy.universal._trace.get_call_counts 4 | get_durations = hpy.universal._trace.get_durations 5 | set_trace_functions = hpy.universal._trace.set_trace_functions 6 | get_frequency = hpy.universal._trace.get_frequency 7 | -------------------------------------------------------------------------------- /hpy/universal/src/ctx.c: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | #include "handles.h" 3 | 4 | #include "hpy/runtime/ctx_funcs.h" 5 | #include "hpy/runtime/ctx_type.h" 6 | #include "ctx_meth.h" 7 | #include "ctx_misc.h" 8 | 9 | #include "autogen_ctx_impl.h" 10 | #include "autogen_ctx_def.h" 11 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/buildvalue.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_COMMON_RUNTIME_BUILDVALUE_H 2 | #define HPY_COMMON_RUNTIME_BUILDVALUE_H 3 | 4 | #include "hpy.h" 5 | 6 | HPyAPI_HELPER HPy 7 | HPy_BuildValue(HPyContext *ctx, const char *fmt, ...); 8 | 9 | #endif /* HPY_COMMON_RUNTIME_BUILDVALUE_H */ 10 | -------------------------------------------------------------------------------- /docs/api-reference/public-api.rst: -------------------------------------------------------------------------------- 1 | Public API Header 2 | ================= 3 | 4 | The core API is defined in `public_api.h 5 | `_: 6 | 7 | .. literalinclude:: ../../hpy/tools/autogen/public_api.h 8 | :language: c 9 | :linenos: 10 | -------------------------------------------------------------------------------- /docs/examples/mixed-example/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from os import path 3 | 4 | setup( 5 | name="hpy-mixed-example", 6 | hpy_ext_modules=[ 7 | Extension('mixed', sources=[path.join(path.dirname(__file__), 'mixed.c')]), 8 | ], 9 | setup_requires=['hpy'], 10 | ) 11 | -------------------------------------------------------------------------------- /docs/examples/simple-example/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from os import path 3 | 4 | setup( 5 | name="hpy-simple-example", 6 | hpy_ext_modules=[ 7 | Extension('simple', sources=[path.join(path.dirname(__file__), 'simple.c')]), 8 | ], 9 | setup_requires=['hpy'], 10 | ) 11 | -------------------------------------------------------------------------------- /docs/porting-example/steps/setup00.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, Extension 4 | 5 | 6 | setup( 7 | name="hpy-porting-example", 8 | ext_modules=[ 9 | Extension("step_00_c_api", sources=["step_00_c_api.c"]) 10 | ], 11 | py_modules=["step_00_c_api"], 12 | ) 13 | -------------------------------------------------------------------------------- /docs/examples/debug-example.py: -------------------------------------------------------------------------------- 1 | # Run with HPY=debug 2 | import hpy.debug 3 | import snippets 4 | 5 | hpy.debug.set_handle_stack_trace_limit(16) 6 | from hpy.debug.pytest import LeakDetector 7 | with LeakDetector() as ld: 8 | snippets.test_leak_stacktrace() 9 | 10 | print("SUCCESS") # Should not be actually printed 11 | -------------------------------------------------------------------------------- /docs/examples/trace-example.py: -------------------------------------------------------------------------------- 1 | # Run with HPY=trace 2 | from hpy.trace import get_call_counts 3 | import snippets 4 | 5 | add_count_0 = get_call_counts()["ctx_Add"] 6 | snippets.add(1, 2) == 3 7 | add_count_1 = get_call_counts()["ctx_Add"] 8 | 9 | print('get_call_counts()["ctx_Add"] == %d' % (add_count_1 - add_count_0)) 10 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_err.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | #include "hpy/runtime/ctx_funcs.h" 4 | 5 | #ifndef HPY_ABI_CPYTHON 6 | // for _h2py and _py2h 7 | # include "handles.h" 8 | #endif 9 | 10 | _HPy_HIDDEN int 11 | ctx_Err_Occurred(HPyContext *ctx) { 12 | return PyErr_Occurred() ? 1 : 0; 13 | } 14 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-ctx.rst: -------------------------------------------------------------------------------- 1 | HPy Context 2 | =========== 3 | 4 | The ``HPyContext`` structure is also part of the API since it provides handles 5 | for built-in objects. For a high-level description of the context, please also 6 | read :ref:`api:hpycontext`. 7 | 8 | .. autocstruct:: hpy/cpython/autogen_ctx.h::_HPyContext_s 9 | :members: 10 | -------------------------------------------------------------------------------- /microbench/pixi.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | authors = ["The HPy team "] 3 | channels = ["conda-forge"] 4 | name = "hpy.microbench" 5 | platforms = ["linux-64"] 6 | version = "0.1.0" 7 | 8 | [tasks] 9 | 10 | [dependencies] 11 | gcc = ">=15.1.0,<15.2" 12 | valgrind = ">=3.25.0,<4" 13 | libffi = ">=3.4.6,<4" 14 | libxcrypt = ">=4.4.36,<5" 15 | -------------------------------------------------------------------------------- /docs/examples/quickstart/setup.py: -------------------------------------------------------------------------------- 1 | # setup.py 2 | 3 | from setuptools import setup, Extension 4 | from os import path 5 | 6 | DIR = path.dirname(__file__) 7 | setup( 8 | name="hpy-quickstart", 9 | hpy_ext_modules=[ 10 | Extension('quickstart', sources=[path.join(DIR, 'quickstart.c')]), 11 | ], 12 | setup_requires=['hpy'], 13 | ) 14 | -------------------------------------------------------------------------------- /docs/examples/hpytype-example/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from os import path 3 | 4 | setup( 5 | name="hpy-type-example", 6 | hpy_ext_modules=[ 7 | Extension('simple_type', sources=['simple_type.c']), 8 | Extension('builtin_type', sources=['builtin_type.c']), 9 | ], 10 | setup_requires=['hpy'], 11 | ) 12 | -------------------------------------------------------------------------------- /docs/porting-example/steps/setup01.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, Extension 4 | 5 | 6 | setup( 7 | name="hpy-porting-example", 8 | hpy_ext_modules=[ 9 | Extension("step_01_hpy_legacy", sources=["step_01_hpy_legacy.c"]) 10 | ], 11 | py_modules=["step_01_hpy_legacy"], 12 | setup_requires=['hpy'], 13 | ) 14 | -------------------------------------------------------------------------------- /docs/porting-example/steps/setup02.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, Extension 4 | 5 | 6 | setup( 7 | name="hpy-porting-example", 8 | hpy_ext_modules=[ 9 | Extension("step_02_hpy_legacy", sources=["step_02_hpy_legacy.c"]) 10 | ], 11 | py_modules=["step_02_hpy_legacy"], 12 | setup_requires=['hpy'], 13 | ) 14 | -------------------------------------------------------------------------------- /hpy/debug/__init__.py: -------------------------------------------------------------------------------- 1 | from .leakdetector import HPyDebugError, HPyLeakError, LeakDetector 2 | 3 | 4 | def set_handle_stack_trace_limit(limit): 5 | from hpy.universal import _debug 6 | _debug.set_handle_stack_trace_limit(limit) 7 | 8 | 9 | def disable_handle_stack_traces(): 10 | from hpy.universal import _debug 11 | _debug.set_handle_stack_trace_limit(None) 12 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/forbid_python_h/Python.h: -------------------------------------------------------------------------------- 1 | // sanity check 2 | #ifndef HPY_ABI_UNIVERSAL 3 | # error "Internal HPy error, something is wrong with your build system. The directory hpy/forbid_python_h has been added to the include_dirs but the target ABI does not seems to be 'universal'" 4 | #endif 5 | 6 | #error "It is forbidden to #include when targeting the HPy Universal ABI" 7 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-err.rst: -------------------------------------------------------------------------------- 1 | Exception Handling 2 | ================== 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPyErr_SetFromErrnoWithFilename, HPyErr_SetFromErrnoWithFilenameObjects, HPy_FatalError, HPyErr_SetString, HPyErr_SetObject, HPyErr_Occurred, HPyErr_ExceptionMatches, HPyErr_NoMemory, HPyErr_Clear, HPyErr_NewException, HPyErr_NewExceptionWithDoc, HPyErr_WarnEx, HPyErr_WriteUnraisable 6 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/hpyexports.h: -------------------------------------------------------------------------------- 1 | #ifndef HPy_EXPORTS_H 2 | #define HPy_EXPORTS_H 3 | 4 | // Copied from Python's exports.h 5 | #ifndef HPy_EXPORTED_SYMBOL 6 | #if defined(_WIN32) || defined(__CYGWIN__) 7 | #define HPy_EXPORTED_SYMBOL __declspec(dllexport) 8 | #else 9 | #define HPy_EXPORTED_SYMBOL __attribute__ ((visibility ("default"))) 10 | #endif 11 | #endif 12 | 13 | #endif /* HPy_EXPORTS_H */ 14 | -------------------------------------------------------------------------------- /hpy/universal/src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_API_H 2 | #define HPY_API_H 3 | 4 | #include "hpy.h" 5 | 6 | extern struct _HPyContext_s g_universal_ctx; 7 | 8 | /* declare alloca() */ 9 | #if defined(_MSC_VER) 10 | # include /* for alloca() */ 11 | #else 12 | # include 13 | # if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) 14 | # include 15 | # endif 16 | #endif 17 | 18 | #endif /* HPY_API_H */ 19 | -------------------------------------------------------------------------------- /docs/examples/snippets/legacyinit.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // BEGIN 4 | static struct PyModuleDef mod_def = { 5 | PyModuleDef_HEAD_INIT, 6 | .m_name = "legacyinit", 7 | .m_size = -1 8 | }; 9 | 10 | PyMODINIT_FUNC 11 | PyInit_legacyinit(void) 12 | { 13 | PyObject *mod = PyModule_Create(&mod_def); 14 | if (mod == NULL) return NULL; 15 | 16 | // Some initialization: add types, constants, ... 17 | 18 | return mod; 19 | } 20 | // END -------------------------------------------------------------------------------- /docs/porting-example/steps/setup03.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, Extension 4 | 5 | # now we can add --hpy-abi=universal to the invocation of setup.py to build a 6 | # universal binary 7 | setup( 8 | name="hpy-porting-example", 9 | hpy_ext_modules=[ 10 | Extension("step_03_hpy_final", sources=["step_03_hpy_final.c"]) 11 | ], 12 | py_modules=["step_03_hpy_final"], 13 | setup_requires=['hpy'], 14 | ) 15 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-object.rst: -------------------------------------------------------------------------------- 1 | HPy Object 2 | ========== 3 | 4 | .. autocmodule:: autogen/public_api.h 5 | :members: HPy_IsTrue,HPy_GetAttr,HPy_GetAttr_s,HPy_HasAttr,HPy_HasAttr_s,HPy_SetAttr,HPy_SetAttr_s,HPy_GetItem,HPy_GetItem_s,HPy_GetItem_i,HPy_GetSlice,HPy_SetItem,HPy_SetItem_s,HPy_SetItem_i,HPy_SetSlice,HPy_DelItem,HPy_DelItem_s,HPy_DelItem_i,HPy_DelSlice,HPy_Type,HPy_TypeCheck,HPy_Is,HPy_Repr,HPy_Str,HPy_ASCII,HPy_Bytes,HPy_RichCompare,HPy_RichCompareBool,HPy_Hash,HPy_SetCallFunction 6 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_contextvar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | _HPy_HIDDEN int32_t 10 | ctx_ContextVar_Get(HPyContext *ctx, HPy context_var, HPy default_value, HPy *result) 11 | { 12 | PyObject * py_result; 13 | int32_t ret = PyContextVar_Get(_h2py(context_var), _h2py(default_value), &py_result); 14 | *result = _py2h(py_result); 15 | return ret; 16 | } 17 | -------------------------------------------------------------------------------- /proof-of-concept/pofpackage/foo.c: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | 3 | HPyDef_METH(hello, "hello", HPyFunc_NOARGS) 4 | static HPy hello_impl(HPyContext *ctx, HPy self) 5 | { 6 | return HPyUnicode_FromString(ctx, "hello from pofpackage.foo"); 7 | } 8 | 9 | 10 | static HPyDef *module_defines[] = { 11 | &hello, 12 | NULL 13 | }; 14 | static HPyModuleDef moduledef = { 15 | .doc = "HPy Proof of Concept", 16 | .size = 0, 17 | .defines = module_defines 18 | }; 19 | 20 | HPy_MODINIT(foo, moduledef) 21 | -------------------------------------------------------------------------------- /docs/examples/snippets/hpyinit.c: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | 3 | // BEGIN 4 | HPyDef_SLOT(my_exec, HPy_mod_exec) 5 | int my_exec_impl(HPyContext *ctx, HPy mod) { 6 | 7 | // Some initialization: add types, constants, ... 8 | 9 | return 0; // success 10 | } 11 | 12 | static HPyDef *Methods[] = { 13 | &my_exec, // HPyDef_SLOT macro generated `my_exec` for us 14 | NULL, 15 | }; 16 | 17 | static HPyModuleDef mod_def = { 18 | .defines = Methods 19 | }; 20 | 21 | HPy_MODINIT(hpyinit, mod_def) 22 | // END -------------------------------------------------------------------------------- /c_test/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | INCLUDE=-I.. -I../hpy/devel/include -I../hpy/debug/src/include 3 | CFLAGS = -O0 -UNDEBUG -g -Wall -Werror -Wfatal-errors $(INCLUDE) -DHPY_ABI_UNIVERSAL 4 | 5 | test: test_debug_handles test_stacktrace 6 | ./test_debug_handles 7 | ./test_stacktrace 8 | 9 | test_debug_handles: test_debug_handles.o ../hpy/debug/src/dhqueue.o 10 | $(CC) -o $@ $^ 11 | 12 | test_stacktrace: test_stacktrace.o ../hpy/debug/src/stacktrace.o 13 | $(CC) -o $@ $^ 14 | 15 | %.o : %.c 16 | $(CC) -c $(CFLAGS) $< -o $@ 17 | -------------------------------------------------------------------------------- /c_test/test_stacktrace.c: -------------------------------------------------------------------------------- 1 | // Smoke test for the create_stacktrace function 2 | 3 | #include 4 | #include "acutest.h" // https://github.com/mity/acutest 5 | #include "hpy/debug/src/debug_internal.h" 6 | 7 | void test_create_stacktrace(void) 8 | { 9 | char *trace; 10 | create_stacktrace(&trace, 16); 11 | if (trace != NULL) { 12 | printf("\n\nTRACE:\n%s\n\n", trace); 13 | free(trace); 14 | } 15 | } 16 | 17 | #define MYTEST(X) { #X, X } 18 | 19 | TEST_LIST = { 20 | MYTEST(test_create_stacktrace), 21 | { NULL, NULL } 22 | }; -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_tuple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | 10 | _HPy_HIDDEN HPy 11 | ctx_Tuple_FromArray(HPyContext *ctx, const HPy items[], HPy_ssize_t n) 12 | { 13 | PyObject *res = PyTuple_New(n); 14 | if (!res) 15 | return HPy_NULL; 16 | for(HPy_ssize_t i=0; i=0.9.0; implementation_name == 'cpython'", 13 | "pytest", 14 | "cffi; implementation_name != 'pypy'", 15 | ] 16 | 17 | [build-system] 18 | requires = [ 19 | "setuptools>=64.0", 20 | "hpy>=0.9.0; implementation_name == 'cpython'", 21 | "cffi", 22 | ] 23 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/format.h: -------------------------------------------------------------------------------- 1 | /** 2 | * String formatting helpers. These functions are not part of HPy ABI. The implementation will be linked into HPy extensions. 3 | */ 4 | #ifndef HPY_COMMON_RUNTIME_FORMAT_H 5 | #define HPY_COMMON_RUNTIME_FORMAT_H 6 | 7 | #include "hpy.h" 8 | 9 | HPyAPI_HELPER HPy 10 | HPyUnicode_FromFormat(HPyContext *ctx, const char *fmt, ...); 11 | 12 | HPyAPI_HELPER HPy 13 | HPyUnicode_FromFormatV(HPyContext *ctx, const char *format, va_list vargs); 14 | 15 | HPyAPI_HELPER HPy 16 | HPyErr_Format(HPyContext *ctx, HPy h_type, const char *fmt, ...); 17 | 18 | #endif /* HPY_COMMON_RUNTIME_FORMAT_H */ 19 | -------------------------------------------------------------------------------- /docs/api-reference/inline-helpers.rst: -------------------------------------------------------------------------------- 1 | Inline Helpers 2 | ============== 3 | 4 | **Inline Helper** functions are ``static inline`` functions (written in C). 5 | Those functions are usually small convenience functions that everyone could 6 | write but in order to avoid duplicated effort, they are defined by HPy. 7 | 8 | One category of inline helpers are functions that convert the commonly used 9 | but not fixed width C types, such as ``int``, or ``long long``, to HPy API. 10 | The HPy API always uses well-defined fixed width types like ``int32`` or 11 | ``unsigned int8``. 12 | 13 | .. autocmodule:: hpy/inline_helpers.h 14 | :members: 15 | -------------------------------------------------------------------------------- /hpy/tools/autogen/hpyslot.py: -------------------------------------------------------------------------------- 1 | from pycparser import c_ast 2 | from .autogenfile import AutoGenFile 3 | from .parse import toC 4 | 5 | class autogen_hpyslot_h(AutoGenFile): 6 | PATH = 'hpy/devel/include/hpy/autogen_hpyslot.h' 7 | 8 | def generate(self): 9 | lines = [] 10 | w = lines.append 11 | w('typedef enum {') 12 | for slot in self.api.hpyslots: 13 | w(f' {slot.name} = {slot.value},') 14 | w('} HPySlot_Slot;') 15 | w('') 16 | for slot in self.api.hpyslots: 17 | w(f'#define _HPySlot_SIG__{slot.name} {slot.hpyfunc}') 18 | return '\n'.join(lines) 19 | -------------------------------------------------------------------------------- /docs/api-reference/structseq.rst: -------------------------------------------------------------------------------- 1 | Struct Sequences 2 | ================ 3 | 4 | Struct sequences are subclasses of tuple. Similar to the API for creating 5 | tuples, HPy provides an API to create struct sequences. This is a builder API 6 | such that the struct sequence is guaranteed not to be written after it is 7 | created. 8 | 9 | .. note:: 10 | 11 | There is no specific getter function for struct sequences. Just use one of 12 | :c:func:`HPy_GetItem`, :c:func:`HPy_GetItem_i`, or :c:func:`HPy_GetItem_s`. 13 | 14 | .. autocmodule:: hpy/runtime/structseq.h 15 | :members: HPyStructSequence_Field,HPyStructSequence_Desc,HPyStructSequence_UnnamedField,HPyStructSequence_NewType,HPyStructSequence_New 16 | -------------------------------------------------------------------------------- /microbench/_valgrind_build.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | ffibuilder = FFI() 3 | 4 | ffibuilder.cdef(""" 5 | void callgrind_start(void); 6 | void callgrind_stop(void); 7 | int is_running_on_valgrind(void); 8 | """) 9 | 10 | 11 | ffibuilder.set_source("_valgrind", """ 12 | #include 13 | void callgrind_start(void) { 14 | CALLGRIND_START_INSTRUMENTATION; 15 | } 16 | 17 | void callgrind_stop(void) { 18 | CALLGRIND_STOP_INSTRUMENTATION; 19 | CALLGRIND_DUMP_STATS; 20 | } 21 | 22 | int is_running_on_valgrind(void) { 23 | return RUNNING_ON_VALGRIND; 24 | } 25 | """, 26 | libraries=[]) 27 | 28 | if __name__ == "__main__": 29 | ffibuilder.compile(verbose=True) 30 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-sequence.rst: -------------------------------------------------------------------------------- 1 | HPy Lists and Tuples 2 | ==================== 3 | 4 | Building Tuples and Lists 5 | ------------------------- 6 | 7 | .. autocmodule:: autogen/public_api.h 8 | :members: HPyTupleBuilder_New,HPyTupleBuilder_Set,HPyTupleBuilder_Build,HPyTupleBuilder_Cancel 9 | 10 | .. autocmodule:: autogen/public_api.h 11 | :members: HPyListBuilder_New,HPyListBuilder_Set,HPyListBuilder_Build,HPyListBuilder_Cancel 12 | 13 | Tuples 14 | ------ 15 | 16 | .. autocmodule:: autogen/public_api.h 17 | :members: HPyTuple_Check,HPyTuple_FromArray 18 | 19 | Lists 20 | ----- 21 | 22 | .. autocmodule:: autogen/public_api.h 23 | :members: HPyList_Check,HPyList_New,HPyList_Append,HPyList_Insert 24 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/ctx_type.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_COMMON_RUNTIME_CTX_TYPE_H 2 | #define HPY_COMMON_RUNTIME_CTX_TYPE_H 3 | 4 | #include 5 | #include "hpy.h" 6 | #include "hpy/hpytype.h" 7 | 8 | _HPy_HIDDEN PyMethodDef *create_method_defs(HPyDef *hpydefs[], 9 | PyMethodDef *legacy_methods); 10 | 11 | _HPy_HIDDEN int call_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traverse, 12 | PyObject *self, 13 | cpy_visitproc cpy_visit, 14 | void *cpy_arg); 15 | 16 | #endif /* HPY_COMMON_RUNTIME_CTX_TYPE_H */ 17 | -------------------------------------------------------------------------------- /docs/examples/snippets/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from os import path 3 | 4 | setup( 5 | name="hpy-snippets", 6 | hpy_ext_modules=[ 7 | Extension('hpyvarargs', sources=[path.join(path.dirname(__file__), 'hpyvarargs.c')]), 8 | Extension('snippets', sources=[path.join(path.dirname(__file__), 'snippets.c')]), 9 | Extension('hpyinit', sources=[path.join(path.dirname(__file__), 'hpyinit.c')]), 10 | Extension('hpycall', sources=[path.join(path.dirname(__file__), 'hpycall.c')]), 11 | ], 12 | ext_modules=[ 13 | Extension('legacyinit', sources=[path.join(path.dirname(__file__), 'legacyinit.c')]), 14 | ], 15 | setup_requires=['hpy'], 16 | ) 17 | -------------------------------------------------------------------------------- /microbench/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | 3 | setup( 4 | # Workaround: HPy adds files to the sources list and uses absolute paths. 5 | # Newer setuptools complain about that if package data should be included. 6 | # Therefore, we explicitly disable this here. 7 | include_package_data=False, 8 | ext_modules = [ 9 | Extension('cpy_simple', 10 | ['src/cpy_simple.c'], 11 | extra_compile_args=['-g']) 12 | ], 13 | hpy_ext_modules = [ 14 | Extension('hpy_simple', 15 | ['src/hpy_simple.c'], 16 | extra_compile_args=['-g']), 17 | ], 18 | cffi_modules=["_valgrind_build.py:ffibuilder"], 19 | ) 20 | -------------------------------------------------------------------------------- /hpy/debug/src/debug_ctx_not_cpython.c: -------------------------------------------------------------------------------- 1 | // This is for non-CPython implementations! 2 | // 3 | // If you want to bundle the debug mode into your own version of 4 | // hpy.universal, make sure to compile this file and NOT debug_ctx_cpython.c 5 | 6 | #include "debug_internal.h" 7 | 8 | void debug_ctx_CallRealFunctionFromTrampoline(HPyContext *dctx, 9 | HPyFunc_Signature sig, 10 | void *func, void *args) 11 | { 12 | HPyContext *uctx = get_info(dctx)->uctx; 13 | HPy_FatalError(uctx, 14 | "Something is very wrong! _HPy_CallRealFunctionFromTrampoline() " 15 | "should be used only by the CPython version of hpy.universal"); 16 | } 17 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_eval.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | _HPy_HIDDEN HPy 10 | ctx_Compile_s(HPyContext *ctx, const char *utf8_source, const char *utf8_filename, HPy_SourceKind kind) 11 | { 12 | int start; 13 | switch (kind) 14 | { 15 | case HPy_SourceKind_Expr: start = Py_eval_input; break; 16 | case HPy_SourceKind_File: start = Py_file_input; break; 17 | case HPy_SourceKind_Single: start = Py_single_input; break; 18 | default: 19 | PyErr_SetString(PyExc_SystemError, "invalid source kind"); 20 | return HPy_NULL; 21 | } 22 | return _py2h(Py_CompileString(utf8_source, utf8_filename, start)); 23 | } 24 | -------------------------------------------------------------------------------- /hpy/tools/valgrind/hpy.supp: -------------------------------------------------------------------------------- 1 | { 2 | <_HPyModuleDef_CreatePyModuleDef_leak> 3 | Memcheck:Leak 4 | match-leak-kinds: definite,indirect 5 | ... 6 | fun:_HPyModuleDef_CreatePyModuleDef 7 | ... 8 | } 9 | 10 | { 11 | 12 | Memcheck:Leak 13 | match-leak-kinds: definite 14 | ... 15 | fun:ctx_Type_FromSpec 16 | ... 17 | } 18 | 19 | { 20 | 21 | Memcheck:Leak 22 | match-leak-kinds: definite 23 | ... 24 | fun:PyUnicode_New.part.42 25 | ... 26 | } 27 | 28 | { 29 | 30 | Memcheck:Leak 31 | match-leak-kinds: definite 32 | ... 33 | fun:PyUnicode_New 34 | ... 35 | } 36 | 37 | { 38 | 39 | Memcheck:Leak 40 | match-leak-kinds: definite 41 | ... 42 | fun:PyLong_FromLong 43 | ... 44 | } 45 | -------------------------------------------------------------------------------- /docs/porting-example/steps/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ Pytest configuration for the porting example tests. """ 4 | 5 | import glob 6 | import os 7 | import sys 8 | 9 | import pytest 10 | 11 | 12 | sys.path.insert(0, os.path.join(os.path.dirname(__file__))) 13 | 14 | 15 | class PortingStep: 16 | def __init__(self, src): 17 | self.name = os.path.splitext(os.path.basename(src))[0] 18 | self.src = src 19 | 20 | def import_step(self): 21 | return __import__(self.name) 22 | 23 | 24 | PORTING_STEPS = [ 25 | PortingStep(src) for src in sorted( 26 | glob.glob(os.path.join(os.path.dirname(__file__), "step_*.c"))) 27 | ] 28 | 29 | 30 | @pytest.fixture( 31 | params=PORTING_STEPS, 32 | ids=[step.name for step in PORTING_STEPS], 33 | ) 34 | def step(request): 35 | return request.param 36 | -------------------------------------------------------------------------------- /hpy/tools/autogen/__init__.py: -------------------------------------------------------------------------------- 1 | import pycparser 2 | from packaging import version 3 | 4 | if version.parse(pycparser.__version__) < version.parse('2.21'): 5 | raise ImportError('You need pycparser>=2.21 to run autogen') 6 | 7 | from .parse import HPyAPI, AUTOGEN_H 8 | 9 | 10 | def generate(generators, *gen_args): 11 | """ 12 | Takes a sequence of autogen generators that will have access to the parse 13 | tree of 'public_api.c' and can then generate files or whatever. 14 | :param generators: A sequence (e.g. tuple) of classes or callables that 15 | will produce objects with a 'write' method. The 'gen_args' will be passed 16 | to the 'write' method on invocation. 17 | :param gen_args: Arguments for the autogen generator instances. 18 | """ 19 | api = HPyAPI.parse(AUTOGEN_H) 20 | for cls in generators: 21 | cls(api).write(*gen_args) 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | livehtml: 23 | @sphinx-autobuild \ 24 | "$(SOURCEDIR)" \ 25 | -b html \ 26 | --ignore "*~" \ 27 | --ignore ".#*" \ 28 | $(SPHINXOPTS) $(BUILDDIR)/html 29 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/argparse.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_COMMON_RUNTIME_ARGPARSE_H 2 | #define HPY_COMMON_RUNTIME_ARGPARSE_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include "hpy.h" 8 | 9 | HPyAPI_HELPER int 10 | HPyArg_Parse(HPyContext *ctx, HPyTracker *ht, const HPy *args, 11 | size_t nargs, const char *fmt, ...); 12 | 13 | HPyAPI_HELPER int 14 | HPyArg_ParseKeywords(HPyContext *ctx, HPyTracker *ht, const HPy *args, 15 | size_t nargs, HPy kwnames, const char *fmt, 16 | const char *keywords[], ...); 17 | 18 | HPyAPI_HELPER int 19 | HPyArg_ParseKeywordsDict(HPyContext *ctx, HPyTracker *ht, const HPy *args, 20 | HPy_ssize_t nargs, HPy kw, const char *fmt, 21 | const char *keywords[], ...); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | #endif /* HPY_COMMON_RUNTIME_ARGPARSE_H */ 27 | -------------------------------------------------------------------------------- /proof-of-concept/pofpackage/bar.cpp: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | 3 | class Bar 4 | { 5 | int foo; 6 | public: 7 | Bar(int f) 8 | { 9 | foo = f; 10 | } 11 | 12 | int boo(HPyContext *ctx, HPy obj) 13 | { 14 | return foo + HPyLong_AsLong(ctx, obj); 15 | } 16 | }; 17 | 18 | HPyDef_METH(hello, "hello", HPyFunc_O) 19 | static HPy hello_impl(HPyContext *ctx, HPy self, HPy obj) 20 | { 21 | Bar b(21); 22 | return HPyLong_FromLong(ctx, b.boo(ctx, obj)); 23 | } 24 | 25 | 26 | static HPyDef *module_defines[] = { 27 | &hello, 28 | NULL 29 | }; 30 | static HPyModuleDef moduledef = { 31 | .doc = "HPy C++ Proof of Concept", 32 | .size = 0, 33 | .defines = module_defines 34 | }; 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | 41 | HPy_MODINIT(bar, moduledef) 42 | 43 | #ifdef __cplusplus 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of HPy's significant contributors. 2 | # 3 | # This does not necessarily list everyone who has contributed code, 4 | # especially since many employees of one corporation may be contributing. 5 | # To see the full list of contributors, see the revision history in 6 | # source control. 7 | Alexander Schremmer 8 | Ammar Askar 9 | Ankith (ankith26) 10 | Antonio Cuni 11 | Armin Rigo 12 | Charlie Hayden 13 | Christian Klein 14 | Daniel Alley 15 | Dominic Davis-Foster 16 | Jaime Resano Aísa 17 | Joachim Trouverie 18 | Julian Berman 19 | Krish Patel 20 | Kyle Lawlor-Bagcal 21 | Mathieu Dupuy 22 | Matti Picus 23 | Max Horn 24 | Maxwell Bernstein 25 | Mohamed Koubaa 26 | Nico Rittinghaus 27 | Omer Katz 28 | Oracle and/or its affiliates 29 | Paul Prescod 30 | Pierre Augier 31 | Robert O'Shea 32 | Ronan Lamy 33 | Simon Cross 34 | Stefan Behnel 35 | Stefano Rivera 36 | Victor Stinner 37 | Vytautas Liuolia 38 | Łukasz Langa 39 | -------------------------------------------------------------------------------- /test/test_hpyimport.py: -------------------------------------------------------------------------------- 1 | from .support import HPyTest 2 | 3 | class TestImport(HPyTest): 4 | 5 | def test_ImportModule(self): 6 | import pytest 7 | import sys 8 | mod = self.make_module(""" 9 | HPyDef_METH(f, "f", HPyFunc_O) 10 | static HPy f_impl(HPyContext *ctx, HPy self, HPy h_name) 11 | { 12 | // we use bytes because ATM we don't have HPyUnicode_AsUTF8 or similar 13 | const char *name = HPyBytes_AsString(ctx, h_name); 14 | if (name == NULL) 15 | return HPy_NULL; 16 | return HPyImport_ImportModule(ctx, name); 17 | } 18 | @EXPORT(f) 19 | @INIT 20 | """) 21 | sys2 = mod.f(b'sys') 22 | assert sys is sys2 23 | with pytest.raises(ImportError): 24 | mod.f(b'This is the name of a module which does not exist, hopefully') 25 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_bytes.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | #include "hpy/runtime/ctx_funcs.h" 4 | 5 | #ifndef HPY_ABI_CPYTHON 6 | // for _h2py and _py2h 7 | # include "handles.h" 8 | #endif 9 | 10 | 11 | _HPy_HIDDEN HPy 12 | ctx_Bytes_FromStringAndSize(HPyContext *ctx, const char *v, HPy_ssize_t len) 13 | { 14 | if (v == NULL) { 15 | // The CPython API allows passing a null pointer to 16 | // PyBytes_FromStringAndSize and returns uninitialized memory of the 17 | // requested size which can then be initialized after the call. 18 | // In HPy the underlying memory is opaque and so cannot be initialized 19 | // after the call and so we raise an error instead. 20 | HPyErr_SetString(ctx, ctx->h_ValueError, 21 | "NULL char * passed to HPyBytes_FromStringAndSize"); 22 | return HPy_NULL; 23 | } 24 | return _py2h(PyBytes_FromStringAndSize(v, len)); 25 | } 26 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/ctx_module.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_COMMON_RUNTIME_CTX_MODULE_H 2 | #define HPY_COMMON_RUNTIME_CTX_MODULE_H 3 | 4 | #include 5 | #include "hpy.h" 6 | 7 | // Helper functions for CPython implementation (both CPython ABI and 8 | // HPy universal module impl for CPython) 9 | 10 | /** Converts HPy module definition to CPython module definition for multiphase 11 | * initialization */ 12 | _HPy_HIDDEN PyModuleDef* 13 | _HPyModuleDef_CreatePyModuleDef(HPyModuleDef *hpydef); 14 | 15 | /** Converts HPy module definition to PyObject that wraps CPython module 16 | * definition for multiphase initialization */ 17 | _HPy_HIDDEN PyObject* 18 | _HPyModuleDef_AsPyInit(HPyModuleDef *hpydef); 19 | 20 | /** Implements the extra HPy specific validation that should be applied to the 21 | * result of the HPy_mod_create slot. */ 22 | _HPy_HIDDEN void 23 | _HPyModule_CheckCreateSlotResult(PyObject **result); 24 | 25 | #endif //HPY_COMMON_RUNTIME_CTX_MODULE_H 26 | -------------------------------------------------------------------------------- /hpy/universal/src/ctx_misc.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_CTX_MISC_H 2 | #define HPY_CTX_MISC_H 3 | 4 | #include "hpy.h" 5 | #include "api.h" 6 | 7 | HPyAPI_IMPL HPy ctx_FromPyObject(HPyContext *ctx, cpy_PyObject *obj); 8 | HPyAPI_IMPL cpy_PyObject *ctx_AsPyObject(HPyContext *ctx, HPy h); 9 | HPyAPI_IMPL void ctx_Close(HPyContext *ctx, HPy h); 10 | HPyAPI_IMPL HPy ctx_Dup(HPyContext *ctx, HPy h); 11 | HPyAPI_IMPL void ctx_Field_Store(HPyContext *ctx, HPy target_object, 12 | HPyField *target_field, HPy h); 13 | HPyAPI_IMPL HPy ctx_Field_Load(HPyContext *ctx, HPy source_object, 14 | HPyField source_field); 15 | HPyAPI_IMPL void ctx_Global_Store(HPyContext *ctx, HPyGlobal *global, HPy h); 16 | HPyAPI_IMPL HPy ctx_Global_Load(HPyContext *ctx, HPyGlobal global); 17 | HPyAPI_IMPL void ctx_FatalError(HPyContext *ctx, const char *message); 18 | HPyAPI_IMPL int ctx_Type_IsSubtype(HPyContext *ctx, HPy sub, HPy type); 19 | 20 | #endif /* HPY_CTX_MISC_H */ 21 | -------------------------------------------------------------------------------- /hpy/tools/autogen/autogenfile.py: -------------------------------------------------------------------------------- 1 | C_DISCLAIMER = """ 2 | /* 3 | DO NOT EDIT THIS FILE! 4 | 5 | This file is automatically generated by {clsname} 6 | See also hpy.tools.autogen and hpy/tools/public_api.h 7 | 8 | Run this to regenerate: 9 | make autogen 10 | 11 | */ 12 | """ 13 | 14 | class AutoGenFile: 15 | LANGUAGE = 'C' 16 | PATH = None 17 | DISCLAIMER = None 18 | 19 | def __init__(self, api): 20 | if self.DISCLAIMER is None and self.LANGUAGE == 'C': 21 | self.DISCLAIMER = C_DISCLAIMER 22 | self.api = api 23 | 24 | def generate(self): 25 | raise NotImplementedError 26 | 27 | def write(self, root): 28 | cls = self.__class__ 29 | clsname = '%s.%s' % (cls.__module__, cls.__name__) 30 | with root.join(self.PATH).open('w', encoding='utf-8') as f: 31 | if self.DISCLAIMER is not None: 32 | f.write(self.DISCLAIMER.format(clsname=clsname)) 33 | f.write('\n') 34 | f.write(self.generate()) 35 | f.write('\n') 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 The HPy Authors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/valgrind-tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | portion: 5 | description: 'Select portion of tests to run under Valgrind (default runs all)' 6 | default: '' 7 | type: string 8 | required: false 9 | 10 | jobs: 11 | valgrind_tests: 12 | # name: Valgrind tests 13 | runs-on: 'ubuntu-latest' 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install / Upgrade system dependencies 18 | run: sudo apt update && sudo apt install -y valgrind 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v5.4 22 | with: 23 | python-version: 3.9 24 | 25 | - name: Install / Upgrade Python dependencies 26 | run: python -m pip install --upgrade pip wheel setuptools 27 | 28 | - name: Build 29 | run: | 30 | make 31 | python -m pip install . 32 | 33 | - name: Run tests 34 | env: 35 | HPY_TEST_PORTION: ${{ inputs.portion }} 36 | run: | 37 | python -m pip install pytest pytest-valgrind pytest-portion filelock 38 | make valgrind 39 | -------------------------------------------------------------------------------- /hpy/tools/autogen/autogen.h: -------------------------------------------------------------------------------- 1 | #define STRINGIFY(X) #X 2 | #define HPy_ID(X) _Pragma(STRINGIFY(id=X)) \ 3 | 4 | #define AUTOGEN 1 5 | 6 | // These are not real typedefs: they are there only to make pycparser happy 7 | typedef int HPy; 8 | typedef int HPyContext; 9 | typedef int HPyModuleDef; 10 | typedef int HPyType_Spec; 11 | typedef int HPyType_SpecParam; 12 | typedef int HPyCFunction; 13 | typedef int HPy_ssize_t; 14 | typedef int HPy_hash_t; 15 | typedef int wchar_t; 16 | typedef int size_t; 17 | typedef int HPyFunc_Signature; 18 | typedef int cpy_PyObject; 19 | typedef int HPyField; 20 | typedef int HPyGlobal; 21 | typedef int HPyListBuilder; 22 | typedef int HPyTupleBuilder; 23 | typedef int HPyTracker; 24 | typedef int HPy_RichCmpOp; 25 | typedef int HPy_buffer; 26 | typedef int HPyFunc_visitproc; 27 | typedef int HPy_UCS4; 28 | typedef int HPyThreadState; 29 | typedef int HPyType_BuiltinShape; 30 | typedef int _HPyCapsule_key; 31 | typedef int HPyCapsule_Destructor; 32 | typedef int int32_t; 33 | typedef int uint32_t; 34 | typedef int int64_t; 35 | typedef int uint64_t; 36 | typedef int bool; 37 | typedef int HPy_SourceKind; 38 | typedef int HPyCallFunction; 39 | 40 | #include "public_api.h" 41 | -------------------------------------------------------------------------------- /test/hpy_devel/test_abitag.py: -------------------------------------------------------------------------------- 1 | from hpy.devel.abitag import parse_ext_suffix, get_hpy_ext_suffix, HPY_ABI_TAG 2 | 3 | def test_parse_ext_suffix_ext(): 4 | _, ext = parse_ext_suffix('.cpython-310-x86_64-linux-gnu.so') 5 | assert ext == 'so' 6 | 7 | def test_parse_ext_suffix_abi_tag(): 8 | def abi_tag(suffix): 9 | tag, ext = parse_ext_suffix(suffix) 10 | return tag 11 | 12 | assert abi_tag('.cpython-38-x86_64-linux-gnu.so') == 'cp38' 13 | assert abi_tag('.cpython-38d-x86_64-linux-gnu.so') == 'cp38d' 14 | assert abi_tag('.cpython-310-x86_64-linux-gnu.so') == 'cp310' 15 | assert abi_tag('.cpython-310d-x86_64-linux-gnu.so') == 'cp310d' 16 | assert abi_tag('.cpython-310-darwin.so') == 'cp310' 17 | assert abi_tag('.cp310-win_amd64.pyd') == 'cp310' 18 | assert abi_tag('.pypy38-pp73-x86_64-linux-gnu.so') == 'pypy38-pp73' 19 | assert abi_tag('.graalpy-38-native-x86_64-darwin.dylib') == 'graalpy-38-native' 20 | 21 | def test_get_hpy_ext_suffix(): 22 | get = get_hpy_ext_suffix 23 | hpy0 = HPY_ABI_TAG 24 | assert get('universal', '.cpython-38-x86_64-linux-gnu.so') == f'.{hpy0}.so' 25 | assert get('hybrid', '.cpython-38-x86_64-linux-gnu.so') == f'.{hpy0}-cp38.so' 26 | -------------------------------------------------------------------------------- /hpy/debug/pytest.py: -------------------------------------------------------------------------------- 1 | # hpy.debug / pytest integration 2 | 3 | import pytest 4 | from .leakdetector import LeakDetector 5 | 6 | # For now "hpy_debug" just does leak detection, but in the future it might 7 | # grows extra features: that's why it's called generically "hpy_debug" instead 8 | # of "detect_leaks". 9 | 10 | # NOTE: the fixture itself is currently untested :(. It turns out that testing 11 | # that the fixture raises during the teardown is complicated and probably 12 | # requires to write a full-fledged plugin. We might want to turn this into a 13 | # real plugin in the future, but for now I think this is enough. 14 | 15 | # pypy still uses a very ancient version of pytest, 2.9.2: pytest<3.x needs to 16 | # use @yield_fixture, which is deprecated in newer version of pytest (where 17 | # you can just use @fixture) 18 | if pytest.__version__ < '3': 19 | fixture = pytest.yield_fixture 20 | else: 21 | fixture = pytest.fixture 22 | 23 | @fixture 24 | def hpy_debug(request): 25 | """ 26 | pytest fixture which makes it possible to control hpy.debug from within a test. 27 | 28 | In particular, it automatically check that the test doesn't leak. 29 | """ 30 | with LeakDetector() as ld: 31 | yield ld 32 | -------------------------------------------------------------------------------- /proof-of-concept/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | import platform 3 | 4 | cpp_compile_extra_args = [] 5 | 6 | if platform.system() == "Windows": 7 | compile_extra_args = ['/WX'] 8 | cpp_compile_extra_args = [ 9 | "/std:c++latest", # MSVC C7555 10 | ] 11 | else: 12 | compile_extra_args = ['-Werror'] 13 | 14 | 15 | setup( 16 | name="hpy-pof", 17 | packages = ['pofpackage'], 18 | zip_safe=False, 19 | hpy_ext_modules=[ 20 | Extension('pof', 21 | sources=['pof.c'], 22 | extra_compile_args=compile_extra_args), 23 | Extension('pofpackage.foo', 24 | sources=['pofpackage/foo.c'], 25 | extra_compile_args=compile_extra_args), 26 | Extension('pofcpp', 27 | sources=['pofcpp.cpp'], 28 | language='C++', 29 | extra_compile_args=compile_extra_args + cpp_compile_extra_args), 30 | Extension('pofpackage.bar', 31 | sources=['pofpackage/bar.cpp'], 32 | language='C++', 33 | extra_compile_args=compile_extra_args + cpp_compile_extra_args), 34 | ], 35 | setup_requires=['hpy'], 36 | ) 37 | -------------------------------------------------------------------------------- /hpy/debug/leakdetector.py: -------------------------------------------------------------------------------- 1 | from hpy.universal import _debug 2 | 3 | class HPyDebugError(Exception): 4 | pass 5 | 6 | class HPyLeakError(HPyDebugError): 7 | def __init__(self, leaks): 8 | super().__init__() 9 | self.leaks = leaks 10 | 11 | def __str__(self): 12 | lines = [] 13 | n = len(self.leaks) 14 | s = 's' if n != 1 else '' 15 | lines.append(f'{n} unclosed handle{s}:') 16 | for dh in self.leaks: 17 | lines.append(' %r' % dh) 18 | return '\n'.join(lines) 19 | 20 | 21 | class LeakDetector: 22 | 23 | def __init__(self): 24 | self.generation = None 25 | 26 | def start(self): 27 | if self.generation is not None: 28 | raise ValueError('LeakDetector already started') 29 | self.generation = _debug.new_generation() 30 | 31 | def stop(self): 32 | if self.generation is None: 33 | raise ValueError('LeakDetector not started yet') 34 | leaks = _debug.get_open_handles(self.generation) 35 | if leaks: 36 | raise HPyLeakError(leaks) 37 | 38 | def __enter__(self): 39 | self.start() 40 | return self 41 | 42 | def __exit__(self, etype, evalue, tb): 43 | self.stop() 44 | -------------------------------------------------------------------------------- /docs/examples/simple-example/simple.c: -------------------------------------------------------------------------------- 1 | /* Simple C module that defines single simple function "myabs". 2 | * We need to have a separate standalone package for those snippets, because we 3 | * want to show the source code in its entirety, including the HPyDef array 4 | * initialization, the module definition, and the setup.py script, so there is 5 | * no room left for mixing these code snippets with other code snippets. 6 | */ 7 | 8 | // BEGIN: myabs 9 | #include "hpy.h" 10 | 11 | HPyDef_METH(myabs, "myabs", HPyFunc_O) 12 | static HPy myabs_impl(HPyContext *ctx, HPy self, HPy arg) 13 | { 14 | return HPy_Absolute(ctx, arg); 15 | } 16 | // END: myabs 17 | 18 | // BEGIN: double 19 | HPyDef_METH_IMPL(double_num, "double", double_impl, HPyFunc_O) 20 | static HPy double_impl(HPyContext *ctx, HPy self, HPy arg) 21 | { 22 | return HPy_Add(ctx, arg, arg); 23 | } 24 | // END: double 25 | 26 | // BEGIN: methodsdef 27 | static HPyDef *SimpleMethods[] = { 28 | &myabs, 29 | &double_num, 30 | NULL, 31 | }; 32 | 33 | static HPyModuleDef simple = { 34 | .doc = "HPy Example", 35 | .size = 0, 36 | .defines = SimpleMethods, 37 | .legacy_methods = NULL 38 | }; 39 | // END: methodsdef 40 | 41 | // BEGIN: moduledef 42 | HPy_MODINIT(simple, simple) 43 | // END: moduledef -------------------------------------------------------------------------------- /proof-of-concept/test_pof.py: -------------------------------------------------------------------------------- 1 | import pof 2 | import pofpackage.foo 3 | import pofcpp 4 | import pofpackage.bar 5 | 6 | def test_do_nothing(): 7 | assert pof.do_nothing() is None 8 | 9 | def test_double(): 10 | assert pof.double(21) == 42 11 | 12 | def test_add_ints(): 13 | assert pof.add_ints(30, 12) == 42 14 | 15 | def test_add_ints_kw(): 16 | assert pof.add_ints_kw(b=30, a=12) == 42 17 | 18 | def test_point(): 19 | p = pof.Point(1, 2) 20 | assert repr(p) == 'Point(1, 2)' # fixme when we have HPyFloat_FromDouble 21 | 22 | def test_pofpackage(): 23 | assert pofpackage.foo.__name__ == "pofpackage.foo" 24 | assert pofpackage.foo.hello() == 'hello from pofpackage.foo' 25 | 26 | def test_cpp_do_nothing(): 27 | assert pofcpp.do_nothing() is None 28 | 29 | def test_cpp_double(): 30 | assert pofcpp.double(21) == 42 31 | 32 | def test_cpp_add_ints(): 33 | assert pofcpp.add_ints(30, 12) == 42 34 | 35 | def test_cpp_add_ints_kw(): 36 | assert pofcpp.add_ints_kw(b=30, a=12) == 42 37 | 38 | def test_cpp_point(): 39 | p = pofcpp.Point(1, 2) 40 | assert repr(p) == 'Point(1, 2)' # fixme when we have HPyFloat_FromDouble 41 | 42 | def test_cpp_pofpackage(): 43 | assert pofpackage.bar.__name__ == "pofpackage.bar" 44 | assert pofpackage.bar.hello(21) == 42 45 | -------------------------------------------------------------------------------- /test/test_hpyglobal.py: -------------------------------------------------------------------------------- 1 | """ 2 | NOTE: this tests are also meant to be run as PyPy "applevel" tests. 3 | 4 | This means that global imports will NOT be visible inside the test 5 | functions. In particular, you have to "import pytest" inside the test in order 6 | to be able to use e.g. pytest.raises (which on PyPy will be implemented by a 7 | "fake pytest module") 8 | """ 9 | from .support import HPyTest 10 | 11 | 12 | class TestHPyGlobal(HPyTest): 13 | 14 | def test_basics(self): 15 | mod = self.make_module(""" 16 | HPyGlobal myglobal; 17 | 18 | HPyDef_METH(setg, "setg", HPyFunc_O) 19 | static HPy setg_impl(HPyContext *ctx, HPy self, HPy arg) 20 | { 21 | HPyGlobal_Store(ctx, &myglobal, arg); 22 | return HPy_Dup(ctx, ctx->h_None); 23 | } 24 | 25 | HPyDef_METH(getg, "getg", HPyFunc_NOARGS) 26 | static HPy getg_impl(HPyContext *ctx, HPy self) 27 | { 28 | return HPyGlobal_Load(ctx, myglobal); 29 | } 30 | 31 | @EXPORT(setg) 32 | @EXPORT(getg) 33 | @EXPORT_GLOBAL(myglobal) 34 | @INIT 35 | """) 36 | obj = {'hello': 'world'} 37 | assert mod.setg(obj) is None 38 | assert mod.getg() is obj 39 | -------------------------------------------------------------------------------- /docs/module-state.txt: -------------------------------------------------------------------------------- 1 | How to replace global variables 2 | ------------------------------- 3 | 4 | In a given .c source, write: 5 | 6 | 7 | typedef struct { 8 | long x; 9 | HPy y; 10 | } my_globals_t; 11 | 12 | static void my_globals_traverse(traversefunc traverse, my_globals_t *g) 13 | { 14 | traverse(g->y); 15 | } 16 | 17 | HPyGlobalSpec my_globals = { 18 | .m_size = sizeof(my_globals_t), 19 | .m_traverse = my_globals_traverse 20 | }; 21 | 22 | 23 | There can be several HPyGlobalSpec structures around; in CPython it's done as 24 | part of the PyModuleDef type, but there is no real reason for why it should 25 | be tightly tied to a module. 26 | 27 | 28 | To use: 29 | 30 | my_globals_t *g = HPy_GetState(ctx, &my_globals); 31 | g->x++; 32 | HPy_DoRandomStuffWithHandle(ctx, g->y); 33 | 34 | 35 | Implementation: the type HPyGlobalSpec contains extra internal fields 36 | which should give us a very fast cache: _last_ctx and _last_result, 37 | and HPy_GetState() can be: 38 | 39 | if (ctx == globspec->_last_ctx) 40 | return globspec->_last_result; 41 | else 42 | look up globspec in a dictionary attached to ctx, or vice-versa, 43 | or maybe initialize globspec->_index with a unique incrementing 44 | index and use that to index an array attached to ctx 45 | -------------------------------------------------------------------------------- /docs/examples/mixed-example/mixed.c: -------------------------------------------------------------------------------- 1 | /* Simple C module that shows how to mix CPython API and HPY. 2 | * At the moment, this code is not referenced from the documentation, but it is 3 | * tested nonetheless. 4 | */ 5 | 6 | #include "hpy.h" 7 | 8 | /* a HPy style function */ 9 | HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS) 10 | static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 11 | { 12 | long a, b; 13 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b)) 14 | return HPy_NULL; 15 | return HPyLong_FromLong(ctx, a+b); 16 | } 17 | 18 | 19 | /* Add an old-style function */ 20 | static PyObject * 21 | add_ints2(PyObject *self, PyObject *args) 22 | { 23 | 24 | long a, b, ret; 25 | if (!PyArg_ParseTuple(args, "ll", &a, &b)) 26 | return NULL; 27 | ret = a + b; 28 | return PyLong_FromLong(ret); 29 | } 30 | 31 | static HPyDef *hpy_defines[] = { 32 | &add_ints, 33 | NULL 34 | }; 35 | 36 | static PyMethodDef py_defines[] = { 37 | {"add_ints_legacy", add_ints2, METH_VARARGS, "add two ints"}, 38 | {NULL, NULL, 0, NULL} /* Sentinel */ 39 | }; 40 | 41 | static HPyModuleDef moduledef = { 42 | .doc = "HPy Example of mixing CPython API and HPy API", 43 | .size = 0, 44 | .defines = hpy_defines, 45 | .legacy_methods = py_defines 46 | }; 47 | 48 | 49 | HPy_MODINIT(mixed, moduledef) 50 | -------------------------------------------------------------------------------- /hpy/trace/src/include/hpy_trace.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_TRACE_H 2 | #define HPY_TRACE_H 3 | 4 | #include "hpy.h" 5 | 6 | /* 7 | This is the main public API for the trace mode, and it's meant to be used 8 | by hpy.universal implementations (including but not limited to the 9 | CPython's version of hpy.universal which is included in this repo). 10 | 11 | The idea is that for every uctx there is a corresponding unique tctx which 12 | wraps it. 13 | 14 | If you call hpy_trace_get_ctx twice on the same uctx, you get the same 15 | result. 16 | */ 17 | 18 | HPyContext * hpy_trace_get_ctx(HPyContext *uctx); 19 | int hpy_trace_ctx_init(HPyContext *tctx, HPyContext *uctx); 20 | int hpy_trace_ctx_free(HPyContext *tctx); 21 | int hpy_trace_get_nfunc(void); 22 | const char * hpy_trace_get_func_name(int idx); 23 | 24 | // this is the HPy init function created by HPy_MODINIT. In CPython's version 25 | // of hpy.universal the code is embedded inside the extension, so we can call 26 | // this function directly instead of dlopen it. This is similar to what 27 | // CPython does for its own built-in modules. But we must use the same 28 | // signature as HPy_MODINIT 29 | 30 | #ifdef ___cplusplus 31 | extern "C" 32 | #endif 33 | HPy_EXPORTED_SYMBOL 34 | HPyModuleDef* HPyInit__trace(); 35 | 36 | #ifdef ___cplusplus 37 | extern "C" 38 | #endif 39 | HPy_EXPORTED_SYMBOL 40 | void HPyInitGlobalContext__trace(HPyContext *ctx); 41 | 42 | #endif /* HPY_TRACE_H */ 43 | -------------------------------------------------------------------------------- /hpy/universal/src/handles.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_HANDLES_H 2 | #define HPY_HANDLES_H 3 | 4 | #include 5 | #include "hpy.h" 6 | 7 | // The main reason for +1/-1 is to make sure that if people casts HPy to 8 | // PyObject* directly, things explode. Moreover, with this we can easily 9 | // distinguish normal and debug handles in gdb, by only looking at the last 10 | // bit. 11 | 12 | static inline HPy _py2h(PyObject *obj) { 13 | if (obj == NULL) 14 | return HPy_NULL; 15 | return (HPy){(HPy_ssize_t)obj + 1}; 16 | } 17 | 18 | static inline PyObject *_h2py(HPy h) { 19 | if HPy_IsNull(h) 20 | return NULL; 21 | return (PyObject *)(h._i - 1); 22 | } 23 | 24 | static inline HPyField _py2hf(PyObject *obj) 25 | { 26 | HPy h = _py2h(obj); 27 | return (HPyField){ ._i = h._i }; 28 | } 29 | 30 | static inline PyObject * _hf2py(HPyField hf) 31 | { 32 | HPy h = { ._i = hf._i }; 33 | return _h2py(h); 34 | } 35 | 36 | static inline HPyThreadState _threads2h(PyThreadState* s) 37 | { 38 | return (HPyThreadState) { (intptr_t) s }; 39 | } 40 | 41 | static inline PyThreadState* _h2threads(HPyThreadState h) 42 | { 43 | return (PyThreadState*) h._i; 44 | } 45 | 46 | static inline HPyGlobal _py2hg(PyObject *obj) 47 | { 48 | HPy h = _py2h(obj); 49 | return (HPyGlobal){ ._i = h._i }; 50 | } 51 | 52 | static inline PyObject * _hg2py(HPyGlobal hf) 53 | { 54 | HPy h = { ._i = hf._i }; 55 | return _h2py(h); 56 | } 57 | 58 | #endif /* HPY_HANDLES_H */ 59 | -------------------------------------------------------------------------------- /docs/contributing/index.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Getting Started 5 | --------------- 6 | 7 | TBD 8 | 9 | 10 | Adding New API 11 | -------------- 12 | 13 | 1. Add the function to ``hpy/tools/autogen/public_api.h``. If the CPython 14 | equivalent function name is not the same (after removing the leading ``H``, 15 | add an appropriate CPython function mapping in ``hpy/tools/autogen/conf.py``. 16 | If the signature is complicated or there is no clear equivalent function, 17 | the mapping should be ``None``, and follow the directions in the next step. 18 | Otherwise all the needed functions will be autogenerated. 19 | 20 | 2. If the function cannot be autogenerated (i.e. the mapping does not exist), 21 | you must write the wrapper by hand. Add the function to ``NO_WRAPPER`` in 22 | ``hpy/tools/autogen/debug.py``, and add a ``ctx_fname`` function to 23 | ``hyp/devel/src/runtime/*.c`` (possibly adding the new file to ``setup.py``), 24 | add a debug wrapper to ``hpy/debug/src/debug_ctx.c``, add a implementation 25 | that uses the ``ctx`` variant to ``hpy/devel/include/hpy/cpython/misc.h`` and 26 | add the declaration to ``hpy/devel/include/hpy/runtime/ctx_funcs.h``. 27 | 28 | 3. Run ``make autogen`` which will turn the mapping into autogenerated functions 29 | 30 | 4. Add a test for the functionality 31 | 32 | 5. Build with ``python setup.py build_ext``. After that works, build with 33 | ``python -m pip install -e .``, then run the test with ``python -m pytest ...``. 34 | -------------------------------------------------------------------------------- /docs/examples/snippets/hpyvarargs.c: -------------------------------------------------------------------------------- 1 | /* Simple C module that defines simple functions "myabs" and "add_ints". 2 | * 3 | * This module represents an incremental change over the "simple" package 4 | * and shows how to add a method with VARARGS calling convention. 5 | * 6 | * We need to have a separate standalone C module for those snippets, because we 7 | * want to show the source code including the HPyDef array initialization, so 8 | * there is no room left for adding other entry points for other code snippets. 9 | */ 10 | 11 | #include "hpy.h" 12 | 13 | // This is here to make the module look like an incremental change to simple-example 14 | HPyDef_METH(myabs, "myabs", HPyFunc_O) 15 | static HPy myabs_impl(HPyContext *ctx, HPy self, HPy arg) 16 | { 17 | return HPy_Absolute(ctx, arg); 18 | } 19 | 20 | // BEGIN: add_ints 21 | HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS) 22 | static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 23 | { 24 | long a, b; 25 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b)) 26 | return HPy_NULL; 27 | return HPyLong_FromLong(ctx, a+b); 28 | } 29 | // END: add_ints 30 | 31 | // BEGIN: methodsdef 32 | static HPyDef *SimpleMethods[] = { 33 | &myabs, 34 | &add_ints, 35 | NULL, 36 | }; 37 | // END: methodsdef 38 | 39 | static HPyModuleDef def = { 40 | .doc = "HPy Example of varargs calling convention", 41 | .size = 0, 42 | .defines = SimpleMethods 43 | }; 44 | 45 | HPy_MODINIT(hpyvarargs, def) 46 | -------------------------------------------------------------------------------- /docs/examples/quickstart/quickstart.c: -------------------------------------------------------------------------------- 1 | // quickstart.c 2 | 3 | // This header file is the entrypoint to the HPy API: 4 | #include "hpy.h" 5 | 6 | // HPy method: the HPyDef_METH macro generates some boilerplate code, 7 | // the same code can be also written manually if desired 8 | HPyDef_METH(say_hello, "say_hello", HPyFunc_NOARGS) 9 | static HPy say_hello_impl(HPyContext *ctx, HPy self) 10 | { 11 | // Methods take HPyContext, which must be passed as the first argument to 12 | // all HPy API functions. Other than that HPyUnicode_FromString does the 13 | // same thing as PyUnicode_FromString. 14 | // 15 | // HPy type represents a "handle" to a Python object, but may not be 16 | // a pointer to the object itself. It should be fully "opaque" to the 17 | // users. Try uncommenting the following two lines to see the difference 18 | // from PyObject*: 19 | // 20 | // if (self == self) 21 | // HPyUnicode_FromString(ctx, "Surprise? Try HPy_Is(ctx, self, self)"); 22 | 23 | return HPyUnicode_FromString(ctx, "Hello world"); 24 | } 25 | 26 | static HPyDef *QuickstartMethods[] = { 27 | &say_hello, // 'say_hello' generated for us by the HPyDef_METH macro 28 | NULL, 29 | }; 30 | 31 | static HPyModuleDef quickstart_def = { 32 | .doc = "HPy Quickstart Example", 33 | .defines = QuickstartMethods, 34 | }; 35 | 36 | // The Python interpreter will create the module for us from the 37 | // HPyModuleDef specification. Additional initialization can be 38 | // done in the HPy_mod_exec slot 39 | HPy_MODINIT(quickstart, quickstart_def) 40 | -------------------------------------------------------------------------------- /hpy/tools/autogen/pypy.py: -------------------------------------------------------------------------------- 1 | from .autogenfile import AutoGenFile 2 | from .parse import toC 3 | 4 | # this class should probably be moved somewhere in the PyPy repo 5 | class autogen_pypy_txt(AutoGenFile): 6 | PATH = 'hpy/tools/autogen/autogen_pypy.txt' 7 | LANGUAGE = 'txt' # to avoid inserting the disclaimer 8 | 9 | def generate(self): 10 | lines = [] 11 | w = lines.append 12 | w("typedef struct _HPyContext_s {") 13 | w(" int abi_version;") 14 | for var in self.api.variables: 15 | w(" struct _HPy_s %s;" % var.ctx_name()) 16 | for func in self.api.functions: 17 | w(" void * %s;" % func.ctx_name()) 18 | w("} _struct_HPyContext_s;") 19 | w("") 20 | w("") 21 | # generate stubs for all the API functions 22 | for func in self.api.functions: 23 | w(self.stub(func)) 24 | return '\n'.join(lines) 25 | 26 | def stub(self, func): 27 | signature = toC(func.node) 28 | if func.is_varargs(): 29 | return '# %s' % signature 30 | # 31 | argnames = [p.name for p in func.node.type.args.params] 32 | lines = [] 33 | w = lines.append 34 | w('@API.func("%s")' % signature) 35 | w('def %s(space, %s):' % (func.name, ', '.join(argnames))) 36 | w(' from rpython.rlib.nonconst import NonConstant # for the annotator') 37 | w(' if NonConstant(False): return 0') 38 | w(' raise NotImplementedError') 39 | w('') 40 | return '\n'.join(lines) 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # HPy autogen 2 | hpy/tools/autogen/autogen_pypy.txt 3 | 4 | # generated by setup.py:get_scm_config() 5 | hpy/devel/include/hpy/version.h 6 | hpy/devel/version.py 7 | 8 | # stubs created by setup.py when doing build_ext --inplace 9 | proof-of-concept/pof.py 10 | proof-of-concept/pofpackage/foo.py 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions and static libs 18 | *.so 19 | *.o 20 | *.a 21 | *.lib 22 | vc140.pdb 23 | c_test/test_debug_handles 24 | 25 | # Distribution / packaging 26 | .Python 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | wheels/ 39 | pip-wheel-metadata/ 40 | share/python-wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | MANIFEST 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | test-output.xml 60 | *.cover 61 | *.py,cover 62 | .hypothesis/ 63 | .pytest_cache/ 64 | .hpy.lock 65 | 66 | # Jupyter Notebook 67 | .ipynb_checkpoints 68 | 69 | # IPython 70 | profile_default/ 71 | ipython_config.py 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # Environments 77 | .env 78 | .venv 79 | env/ 80 | venv/ 81 | ENV/ 82 | env.bak/ 83 | venv.bak/ 84 | 85 | # cppcheck 86 | .cppcheck 87 | 88 | # Sphinx 89 | docs/_build/ 90 | 91 | # vscode 92 | .vscode 93 | 94 | microbench/pixi.lock 95 | microbench/.venv_* 96 | microbench/tmp_* 97 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions are welcome, and they are greatly appreciated! Every 5 | little bit helps. 6 | 7 | You can contribute in many ways: 8 | 9 | Types of Contributions 10 | ---------------------- 11 | 12 | ### Report Bugs 13 | 14 | Report bugs at . 15 | 16 | When reporting a bug, please include: 17 | 18 | - Your operating system name and version. 19 | - Any details about your local setup that might be helpful in 20 | troubleshooting. 21 | - Detailed steps to reproduce the bug. 22 | 23 | ### Donate to HPy 24 | 25 | Become a financial contributor and help us sustain the HPy community: [Contribute to HPy](https://opencollective.com/hpy/contribute). 26 | 27 | ### Fix Bugs or Implement Features 28 | 29 | Look through the GitHub issues for bugs or proposals being discussed 30 | or ready for implementation. 31 | 32 | ### Write Documentation 33 | 34 | HPy could always use more documentation, whether as part of the 35 | official HPy docs, in docstrings, or even on the web in blog 36 | posts, articles, and such. 37 | 38 | Get Started! 39 | ------------ 40 | 41 | You can test your environment by running 42 | 43 | ```bash 44 | proof-of-concept/test_pof.sh wheel universal 45 | ``` 46 | 47 | This should build HPy and a proof of concept (demo) extension and 48 | then run a pytest that validates both. 49 | 50 | Until we get around to more comprehensive documentation, you 51 | can reverse engineer how the system works by looking at these 52 | files: 53 | 54 | - `proof-of-concept/test_pof.sh` 55 | - `proof-of-concept/setup.py` 56 | -------------------------------------------------------------------------------- /test/test_support.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pathlib import Path 3 | from . import support 4 | 5 | 6 | def expand_template(template, name): 7 | return support.DefaultExtensionTemplate(template, name).expand() 8 | 9 | 10 | def test_expand_template(): 11 | expanded = expand_template(""" 12 | @EXPORT(f) 13 | @EXPORT(g) 14 | some more C stuff 15 | @INIT 16 | """, name='mytest') 17 | defines_table = ['&f,', '&g,'] 18 | defines = '\n '.join(defines_table) 19 | init_code = support.DefaultExtensionTemplate.INIT_TEMPLATE % { 20 | 'defines': defines, 21 | 'legacy_methods': 'NULL', 22 | 'name': 'mytest', 23 | 'init_types': '', 24 | 'globals_defs': '', 25 | 'globals_field': '', 26 | } 27 | assert expanded.rstrip() == f"""#include 28 | 29 | some more C stuff 30 | {init_code} 31 | """.rstrip() 32 | 33 | 34 | TEST_DIR = Path(__file__).parent 35 | 36 | 37 | def test_source_dump(tmpdir): 38 | import pytest 39 | test_file = 'test_00_basic' 40 | parts = ['TestBasic', 'test_noop_function'] 41 | rc = pytest.main(['--dump-dir=' + str(tmpdir), '::'.join([str(TEST_DIR.joinpath(test_file + ".py"))] + parts)]) 42 | assert rc == 0 43 | expected_dir = '_'.join([test_file] + parts) 44 | print("expected_dir = " + expected_dir) 45 | test_dump_dir = None 46 | for child in Path(tmpdir).iterdir(): 47 | if expected_dir in str(child): 48 | test_dump_dir = Path(child) 49 | assert test_dump_dir and test_dump_dir.exists() 50 | assert test_dump_dir.joinpath('mytest.c').exists() 51 | -------------------------------------------------------------------------------- /microbench/print_other_vs_cpy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pathlib import Path 4 | 5 | try: 6 | other = sys.argv[1] 7 | except IndexError: 8 | other = "PyPy" 9 | 10 | path_result_cpy = Path("tmp_results_cpython.txt") 11 | path_result_other = Path(f"tmp_results_{other.lower()}.txt") 12 | 13 | assert path_result_cpy.exists() 14 | assert path_result_other.exists() 15 | 16 | 17 | def data_from_path(path): 18 | txt = path.read_text() 19 | _, txt = txt.split( 20 | "================================== BENCHMARKS ==================================" 21 | ) 22 | lines = txt.splitlines()[3:-2] 23 | 24 | if "cpy" in path.name: 25 | index_time = 1 26 | else: 27 | parts = lines[0].split() 28 | if len(parts) == 3: 29 | index_time = 1 30 | else: 31 | index_time = 3 32 | 33 | names = [] 34 | times = [] 35 | 36 | for line in lines: 37 | parts = line.split() 38 | names.append(parts[0]) 39 | times.append(float(parts[index_time])) 40 | 41 | return names, times 42 | 43 | 44 | names, times_cpy = data_from_path(path_result_cpy) 45 | names, times_other = data_from_path(path_result_other) 46 | 47 | max_length_name = 45 48 | fmt_name = f"{{:{max_length_name}s}}" 49 | 50 | out = f" {other} HPy univ / CPy native (time ratio, smaller is better) " 51 | num_chars = 81 52 | num_equals = (num_chars - len(out)) // 2 53 | 54 | print("\n" + num_equals * "=" + out + num_equals * "=") 55 | 56 | for index, t_other in enumerate(times_other): 57 | ratio = t_other / times_cpy[index] 58 | name = fmt_name.format(names[index]) 59 | print(f"{name} {ratio:.2f}") 60 | -------------------------------------------------------------------------------- /hpy/universal/src/misc_win32.h: -------------------------------------------------------------------------------- 1 | /************************************************************/ 2 | /* Emulate dlopen()&co. from the Windows API */ 3 | 4 | #define RTLD_LAZY 0 5 | #define RTLD_NOW 0 6 | #define RTLD_GLOBAL 0 7 | #define RTLD_LOCAL 0 8 | 9 | static void *dlopen(const char *filename, int flag) 10 | { 11 | return (void *)LoadLibraryA(filename); 12 | } 13 | 14 | static void *dlopenW(const wchar_t *filename) 15 | { 16 | return (void *)LoadLibraryW(filename); 17 | } 18 | 19 | static void *dlsym(void *handle, const char *symbol) 20 | { 21 | void *address = GetProcAddress((HMODULE)handle, symbol); 22 | #ifndef MS_WIN64 23 | if (!address) { 24 | /* If 'symbol' is not found, then try '_symbol@N' for N in 25 | (0, 4, 8, 12, ..., 124). Unlike ctypes, we try to do that 26 | for any symbol, although in theory it should only be done 27 | for __stdcall functions. 28 | */ 29 | int i; 30 | char mangled_name[1 + strlen(symbol) + 1 + 3 + 1]; 31 | for (i = 0; i < 32; i++) { 32 | sprintf(mangled_name, "_%s@%d", symbol, i * 4); 33 | address = GetProcAddress((HMODULE)handle, mangled_name); 34 | if (address) 35 | break; 36 | } 37 | } 38 | #endif 39 | return address; 40 | } 41 | 42 | static int dlclose(void *handle) 43 | { 44 | return FreeLibrary((HMODULE)handle) ? 0 : -1; 45 | } 46 | 47 | static const char *dlerror(void) 48 | { 49 | static char buf[32]; 50 | DWORD dw = GetLastError(); 51 | if (dw == 0) 52 | return NULL; 53 | sprintf(buf, "error 0x%x", (unsigned int)dw); 54 | return buf; 55 | } 56 | -------------------------------------------------------------------------------- /microbench/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(PYTHON),) 2 | PYTHON := python3 3 | endif 4 | 5 | install: 6 | $(PYTHON) -m pip install . 7 | 8 | install_universal: 9 | $(PYTHON) -m pip install . --config-settings="--global-option=--hpy-abi=universal" 10 | 11 | uninstall: 12 | $(PYTHON) -m pip uninstall hpy.microbench --yes 13 | 14 | test: 15 | $(PYTHON) -m pytest -v | tee tmp_results_$(shell $(PYTHON) -c "import sys; print(sys.implementation.name)").txt 16 | 17 | bench: test 18 | 19 | bench_hpy: 20 | $(PYTHON) -m pytest -v -m hpy | tee tmp_results_$(shell $(PYTHON) -c "import sys; print(sys.implementation.name)").txt 21 | 22 | clean: 23 | rm -f src/*.so src/hpy_simple.py 24 | 25 | cleanall: clean 26 | rm -rf .venv_* tmp_*.txt 27 | 28 | create_venv_cpy: 29 | uv python install 3.12 30 | $(shell uv python find 3.12) -m venv .venv_cpy --upgrade-deps 31 | 32 | create_venv_pypy: 33 | uv python install pypy 34 | $(shell uv python find pypy) -m venv .venv_pypy --upgrade-deps 35 | 36 | create_venv_graalpy: 37 | uv python install graalpy 38 | # cannot use --upgrade-deps because pip is patched for GraalPy 39 | $(shell uv python find graalpy) -m venv .venv_graalpy 40 | 41 | print_cpy: 42 | @echo =================================== CPython ==================================== 43 | @tail tmp_results_cpython.txt -n 29 44 | 45 | print_pypy: 46 | @echo ==================================== PyPy ====================================== 47 | @tail tmp_results_pypy.txt -n 29 48 | 49 | print_graalpy: 50 | @echo =================================== GraalPy ==================================== 51 | @tail tmp_results_graalpy.txt -n 29 52 | 53 | print_pypy_vs_cpy: 54 | @$(PYTHON) print_other_vs_cpy.py PyPy 55 | 56 | print_graalpy_vs_cpy: 57 | @$(PYTHON) print_other_vs_cpy.py GraalPy 58 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_listbuilder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "hpy.h" 4 | 5 | #ifndef HPY_ABI_CPYTHON 6 | // for _h2py and _py2h 7 | # include "handles.h" 8 | #endif 9 | 10 | 11 | _HPy_HIDDEN HPyListBuilder 12 | ctx_ListBuilder_New(HPyContext *ctx, HPy_ssize_t size) 13 | { 14 | PyObject *lst = PyList_New(size); 15 | if (lst == NULL) 16 | PyErr_Clear(); /* delay the MemoryError */ 17 | return (HPyListBuilder){(HPy_ssize_t)lst}; 18 | } 19 | 20 | _HPy_HIDDEN void 21 | ctx_ListBuilder_Set(HPyContext *ctx, HPyListBuilder builder, 22 | HPy_ssize_t index, HPy h_item) 23 | { 24 | PyObject *lst = (PyObject *)builder._lst; 25 | if (lst != NULL) { 26 | PyObject *item = _h2py(h_item); 27 | assert(index >= 0 && index < PyList_GET_SIZE(lst)); 28 | assert(PyList_GET_ITEM(lst, index) == NULL); 29 | Py_INCREF(item); 30 | PyList_SET_ITEM(lst, index, item); 31 | } 32 | } 33 | 34 | _HPy_HIDDEN HPy 35 | ctx_ListBuilder_Build(HPyContext *ctx, HPyListBuilder builder) 36 | { 37 | PyObject *lst = (PyObject *)builder._lst; 38 | if (lst == NULL) { 39 | PyErr_NoMemory(); 40 | return HPy_NULL; 41 | } 42 | builder._lst = 0; 43 | return _py2h(lst); 44 | } 45 | 46 | _HPy_HIDDEN void 47 | ctx_ListBuilder_Cancel(HPyContext *ctx, HPyListBuilder builder) 48 | { 49 | PyObject *lst = (PyObject *)builder._lst; 50 | if (lst == NULL) { 51 | // we don't report the memory error here: the builder 52 | // is being cancelled (so the result of the builder is not being used) 53 | // and likely it's being cancelled during the handling of another error 54 | return; 55 | } 56 | builder._lst = 0; 57 | Py_XDECREF(lst); 58 | } 59 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | HPy Quickstart 2 | ============== 3 | 4 | This section shows how to quickly get started with HPy by creating 5 | a simple HPy extension from scratch. 6 | 7 | Install latest HPy release: 8 | 9 | .. code-block:: console 10 | 11 | python3 -m pip install hpy 12 | 13 | Alternatively, you can also install HPy from the development repository: 14 | 15 | .. code-block:: console 16 | 17 | python3 -m pip install git+https://github.com/hpyproject/hpy.git#egg=hpy 18 | 19 | Create a new directory for the new HPy extension. Location and name of the directory 20 | do not matter. Add the following two files: 21 | 22 | .. literalinclude:: examples/quickstart/quickstart.c 23 | 24 | .. literalinclude:: examples/quickstart/setup.py 25 | :language: python 26 | 27 | Build the extension: 28 | 29 | .. code-block:: console 30 | 31 | python3 setup.py --hpy-abi=universal develop 32 | 33 | Try it out -- start Python console in the same directory and type: 34 | 35 | .. literalinclude:: examples/tests.py 36 | :start-after: test_quickstart 37 | :end-before: # END: test_quickstart 38 | 39 | Notice the shared library that was created by running ``setup.py``: 40 | 41 | .. code-block:: console 42 | 43 | > ls *.so 44 | quickstart.hpy0.so 45 | 46 | It does not have Python version encoded in it. If you happen to have 47 | GraalPy or PyPy installation that supports given HPy version, you can 48 | try running the same extension on it. For example, start 49 | ``$GRAALVM_HOME/bin/graalpy`` in the same directory and type the same 50 | Python code: the extension should load and work just fine. 51 | 52 | Where to go next? 53 | 54 | - :ref:`Simple documented HPy extension example` 55 | - :doc:`Tutorial: porting Python/C API extension to HPy` 56 | -------------------------------------------------------------------------------- /docs/misc/embedding.rst: -------------------------------------------------------------------------------- 1 | Embedding HPy modules 2 | ===================== 3 | 4 | There might be cases where it is beneficial or even necessary to embed multiple 5 | HPy modules into one library. HPy itself already makes use of that. The debug 6 | and the trace module do not have individual libraries but are embedded into the 7 | universal module. 8 | 9 | To achieve that, the embedder will use the macro :c:macro:`HPy_MODINIT` several times. 10 | Unfortunately, this macro defines global state and cannot repeatedly be used by 11 | default. In order to correctly embed several HPy modules into one library, the 12 | embedder needs to consider following: 13 | 14 | * The modules must be compiled with preprocessor macro 15 | :c:macro:`HPY_EMBEDDED_MODULES` defined to enable this feature. 16 | 17 | * There is one major restriction: All HPy-specific module pieces must be 18 | in the same compilation unit. *HPy-specific pieces* are things like the 19 | module's init function (``HPy_MODINIT``) and all slots, members, methods of 20 | the module or any type of it (``HPyDef_*``). The implementation functions 21 | (usually the ``*_impl`` functions) of the slots, members, methods, etc. and 22 | any helper functions may still be in different compilation units. The reason 23 | for this is that the global state induced by ``HPy_MODINIT`` is, of course, 24 | made local (e.g. using C modifier ``static``). 25 | 26 | * It is also necessary to use macro :c:macro:`HPY_MOD_EMBEDDABLE` before the 27 | first usage of any ``HPyDef_*`` macro. 28 | 29 | Also refer to the API reference :ref:`api-reference/hpy-type:hpy module`. 30 | 31 | 32 | **Example** 33 | 34 | .. code-block:: c 35 | 36 | // compile with -DHPY_EMBEDDED_MODULES 37 | 38 | HPY_MOD_EMBEDDABLE(hpymodA) 39 | 40 | HPyDef_METH(foo, /* ... */) 41 | static HPy foo_impl(/* ... */) 42 | { 43 | // ... 44 | } 45 | 46 | HPy_MODINIT(extension_name, hpymodA) 47 | -------------------------------------------------------------------------------- /docs/api-reference/hpy-type.rst: -------------------------------------------------------------------------------- 1 | HPy Types and Modules 2 | ===================== 3 | 4 | Types, modules and their attributes (i.e. methods, members, slots, get-set 5 | descriptors) are defined in a similar way. Section `HPy Type`_ documents the 6 | type-specific and `HPy Module`_ documents the module-specific part. Section 7 | `HPy Definition`_ documents how to define attributes for both, types and 8 | modules. 9 | 10 | 11 | HPy Type 12 | -------- 13 | 14 | Definition 15 | ~~~~~~~~~~ 16 | 17 | .. autocmodule:: hpy/hpytype.h 18 | :members: HPyType_Spec,HPyType_BuiltinShape,HPyType_SpecParam,HPyType_SpecParam_Kind,HPyType_HELPERS,HPyType_LEGACY_HELPERS,HPy_TPFLAGS_DEFAULT,HPy_TPFLAGS_BASETYPE,HPy_TPFLAGS_HAVE_GC 19 | 20 | Construction and More 21 | ~~~~~~~~~~~~~~~~~~~~~ 22 | 23 | .. autocmodule:: autogen/public_api.h 24 | :members: HPyType_FromSpec, HPyType_GetName, HPyType_IsSubtype 25 | 26 | HPy Module 27 | ---------- 28 | 29 | .. c:macro:: HPY_EMBEDDED_MODULES 30 | 31 | If ``HPY_EMBEDDED_MODULES`` is defined, this means that there will be 32 | several embedded HPy modules (and so, several ``HPy_MODINIT`` usages) in the 33 | same binary. In this case, some restrictions apply: 34 | 35 | 1. all of the module's methods/member/slots/... must be defined in the same 36 | file 37 | 2. the embedder **MUST** declare the module to be *embeddable* by using macro 38 | :c:macro:`HPY_MOD_EMBEDDABLE`. 39 | 40 | .. autocmodule:: hpy/hpymodule.h 41 | :members: HPY_MOD_EMBEDDABLE,HPyModuleDef,HPy_MODINIT 42 | 43 | HPy Definition 44 | -------------- 45 | 46 | Defining slots, methods, members, and get-set descriptors for types and modules 47 | is done with HPy definition (represented by C struct :c:struct:`HPyDef`). 48 | 49 | .. autocmodule:: hpy/hpydef.h 50 | :members: HPyDef,HPyDef_Kind,HPySlot,HPyMeth,HPyMember_FieldType,HPyMember,HPyGetSet,HPyDef_SLOT,HPyDef_METH,HPyDef_MEMBER,HPyDef_GET,HPyDef_SET,HPyDef_GETSET,HPyDef_CALL_FUNCTION 51 | -------------------------------------------------------------------------------- /docs/api-reference/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | HPy's public API consists of three parts: 5 | 6 | 1. The **Core API** as defined in the :doc:`public-api` 7 | 2. **HPy Helper** functions 8 | 3. **Inline Helper** functions 9 | 10 | Core API 11 | -------- 12 | 13 | The **Core API** consists of inline functions that call into the Python 14 | interpreter. Those functions will be implemented by each Python interpreter. In 15 | :term:`CPython ABI` mode, many of these inline functions will just delegate to 16 | a C API functions. In :term:`HPy Universal ABI` mode, they will call a function 17 | pointer from the HPy context. This is the source of the performance change 18 | between the modes. 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | function-index 24 | hpy-ctx 25 | hpy-object 26 | hpy-type 27 | hpy-call 28 | hpy-field 29 | hpy-global 30 | hpy-dict 31 | hpy-sequence 32 | hpy-gil 33 | hpy-err 34 | hpy-eval 35 | public-api 36 | 37 | 38 | HPy Helper Functions 39 | -------------------- 40 | 41 | **HPy Helper** functions are functions (written in C) that will be compiled 42 | together with the HPy extension's sources. The appropriate source files are 43 | automatically added to the extension sources. The helper functions will, of 44 | course, use the core API to interact with the interpreter. The main reason for 45 | having the helper functions in the HPy extension is to avoid compatibility 46 | problems due to different compilers. 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | 51 | argument-parsing 52 | build-value 53 | formatting 54 | structseq 55 | helpers 56 | 57 | 58 | Inline Helper Functions 59 | ----------------------- 60 | 61 | **Inline Helper** functions are ``static inline`` functions (written in C). 62 | Those functions are usually small convenience functions that everyone could 63 | write but in order to avoid duplicated effort, they are defined by HPy. 64 | 65 | .. toctree:: 66 | :maxdepth: 2 67 | 68 | inline-helpers 69 | 70 | -------------------------------------------------------------------------------- /hpy/universal/src/ctx_misc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | #include "handles.h" 4 | #include "ctx_misc.h" 5 | 6 | HPyAPI_IMPL HPy 7 | ctx_FromPyObject(HPyContext *ctx, cpy_PyObject *obj) 8 | { 9 | Py_XINCREF(obj); 10 | return _py2h(obj); 11 | } 12 | 13 | HPyAPI_IMPL cpy_PyObject * 14 | ctx_AsPyObject(HPyContext *ctx, HPy h) 15 | { 16 | PyObject *obj = _h2py(h); 17 | Py_XINCREF(obj); 18 | return obj; 19 | } 20 | 21 | HPyAPI_IMPL void 22 | ctx_Close(HPyContext *ctx, HPy h) 23 | { 24 | PyObject *obj = _h2py(h); 25 | Py_XDECREF(obj); 26 | } 27 | 28 | HPyAPI_IMPL HPy 29 | ctx_Dup(HPyContext *ctx, HPy h) 30 | { 31 | PyObject *obj = _h2py(h); 32 | Py_XINCREF(obj); 33 | return _py2h(obj); 34 | } 35 | 36 | HPyAPI_IMPL void 37 | ctx_Field_Store(HPyContext *ctx, HPy target_object, HPyField *target_field, HPy h) 38 | { 39 | PyObject *obj = _h2py(h); 40 | PyObject *target_py_obj = _hf2py(*target_field); 41 | Py_XINCREF(obj); 42 | *target_field = _py2hf(obj); 43 | Py_XDECREF(target_py_obj); 44 | } 45 | 46 | HPyAPI_IMPL HPy 47 | ctx_Field_Load(HPyContext *ctx, HPy source_object, HPyField source_field) 48 | { 49 | PyObject *obj = _hf2py(source_field); 50 | Py_INCREF(obj); 51 | return _py2h(obj); 52 | } 53 | 54 | 55 | HPyAPI_IMPL void 56 | ctx_Global_Store(HPyContext *ctx, HPyGlobal *global, HPy h) 57 | { 58 | PyObject *obj = _h2py(h); 59 | Py_XDECREF(_hg2py(*global)); 60 | Py_XINCREF(obj); 61 | *global = _py2hg(obj); 62 | } 63 | 64 | HPyAPI_IMPL HPy 65 | ctx_Global_Load(HPyContext *ctx, HPyGlobal global) 66 | { 67 | PyObject *obj = _hg2py(global); 68 | Py_INCREF(obj); 69 | return _py2h(obj); 70 | } 71 | 72 | HPyAPI_IMPL void 73 | ctx_FatalError(HPyContext *ctx, const char *message) 74 | { 75 | Py_FatalError(message); 76 | } 77 | 78 | HPyAPI_IMPL int 79 | ctx_Type_IsSubtype(HPyContext *ctx, HPy sub, HPy type) 80 | { 81 | return PyType_IsSubtype((PyTypeObject *)_h2py(sub), 82 | (PyTypeObject *)_h2py(type)); 83 | } 84 | -------------------------------------------------------------------------------- /docs/porting-example/steps/test_porting_example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ Porting example tests. """ 4 | 5 | import pytest 6 | import math 7 | import types 8 | 9 | 10 | class TestPorting: 11 | def test_load_module(self, step): 12 | mod = step.import_step() 13 | assert isinstance(mod, types.ModuleType) 14 | assert mod.__name__ == step.name 15 | assert mod.__doc__.startswith("Point module (Step ") 16 | assert type(mod.Point) == type 17 | assert mod.Point.__doc__.startswith("Point (Step ") 18 | assert isinstance(mod.dot, types.BuiltinFunctionType) 19 | assert mod.dot.__doc__ == "Dot product." 20 | 21 | def test_create_point(self, step): 22 | mod = step.import_step() 23 | p = mod.Point(1, 2) 24 | assert type(p) == mod.Point 25 | 26 | def test_norm(self, step): 27 | mod = step.import_step() 28 | assert mod.Point(1, 2).norm() == math.sqrt(5.0) 29 | assert mod.Point(1.5).norm() == 1.5 30 | 31 | def test_dot(self, step): 32 | mod = step.import_step() 33 | p1 = mod.Point(1, 2) 34 | p2 = mod.Point(3, 2) 35 | assert mod.dot(p1, p2) == 7.0 36 | 37 | def test_object(self, step): 38 | mod = step.import_step() 39 | p1 = mod.Point(23, 42, ...) 40 | assert p1.obj is ... 41 | p2 = mod.Point(23, 42) 42 | assert p2.obj is None 43 | 44 | def test_leak_checker(self, step): 45 | if "hpy_final" not in step.name: 46 | pytest.skip("Can only check for leaks in universal mode") 47 | mod = step.import_step() 48 | import hpy.debug 49 | hpy.debug.set_handle_stack_trace_limit(10) 50 | with hpy.debug.LeakDetector(): 51 | p1 = mod.Point(1, 2, ...) 52 | p2 = mod.Point(3, 2) 53 | 54 | assert p1.obj is ... 55 | assert p2.obj is None 56 | assert p1.norm() == math.sqrt(5.0) 57 | assert mod.dot(p1, p2) == 7.0 58 | 59 | del p1 60 | del p2 61 | -------------------------------------------------------------------------------- /test/test_contextvar.py: -------------------------------------------------------------------------------- 1 | """ 2 | NOTE: this tests are also meant to be run as PyPy "applevel" tests. 3 | 4 | This means that global imports will NOT be visible inside the test 5 | functions. In particular, you have to "import pytest" inside the test in order 6 | to be able to use e.g. pytest.raises (which on PyPy will be implemented by a 7 | "fake pytest module") 8 | """ 9 | from .support import HPyTest 10 | 11 | 12 | class TestHPyContextVar(HPyTest): 13 | 14 | def test_basics(self): 15 | mod = self.make_module(""" 16 | HPyDef_METH(new_ctxv, "new_ctxv", HPyFunc_NOARGS) 17 | static HPy new_ctxv_impl(HPyContext *ctx, HPy self) 18 | { 19 | return HPyContextVar_New(ctx, "test_contextvar", HPy_NULL); 20 | } 21 | 22 | HPyDef_METH(set_ctxv, "set_ctxv", HPyFunc_VARARGS) 23 | static HPy set_ctxv_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 24 | { 25 | HPy obj, val; 26 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "OO", &obj, &val)) 27 | return HPy_NULL; 28 | return HPyContextVar_Set(ctx, obj, val); 29 | } 30 | HPyDef_METH(get_ctxv, "get_ctxv", HPyFunc_VARARGS) 31 | static HPy get_ctxv_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 32 | { 33 | HPy obj, def=HPy_NULL, val; 34 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "O|O", &obj, &def)) 35 | return HPy_NULL; 36 | if (HPyContextVar_Get(ctx, obj, def, &val) < 0) { 37 | return HPy_NULL; 38 | } 39 | return val; 40 | } 41 | 42 | 43 | @EXPORT(new_ctxv) 44 | @EXPORT(get_ctxv) 45 | @EXPORT(set_ctxv) 46 | @INIT 47 | """) 48 | var = mod.new_ctxv() 49 | tok = mod.set_ctxv(var, 4) 50 | assert tok.var is var 51 | four = mod.get_ctxv(var) 52 | assert four == 4 53 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/cpy_types.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_UNIVERSAL_CPY_TYPES_H 2 | #define HPY_UNIVERSAL_CPY_TYPES_H 3 | 4 | /* ~~~~~~~~~~~~~~~~ CPython legacy types ~~~~~~~~~~~~~~~~ */ 5 | 6 | /* These are the types which are needed to implement legacy features such as 7 | .legacy_slots, .legacy_methods, HPy_FromPyObject, HPy_AsPyObject, etc. 8 | 9 | In cpython and hybrid ABI mode we can #include Python.h and use the "real" 10 | types. 11 | 12 | In universal ABI mode, legacy features cannot be used, but we still need 13 | the corresponding C types to use in the HPy declarations. Note that we use 14 | only forward declarations, meaning that it will actually be impossible to 15 | instantiate any of these struct. 16 | */ 17 | 18 | #ifdef HPY_ABI_UNIVERSAL 19 | 20 | typedef struct FORBIDDEN_cpy_PyObject cpy_PyObject; 21 | typedef struct FORBIDDEN_PyMethodDef cpy_PyMethodDef; 22 | typedef struct FORBIDDEN_PyModuleDef cpy_PyModuleDef; 23 | typedef struct FORBIDDEN_bufferinfo cpy_Py_buffer; 24 | 25 | // declare the following API functions as _HPY_LEGACY, which triggers an 26 | // #error if they are used 27 | HPyAPI_FUNC _HPY_LEGACY cpy_PyObject *HPy_AsPyObject(HPyContext *ctx, HPy h); 28 | HPyAPI_FUNC _HPY_LEGACY HPy HPy_FromPyObject(HPyContext *ctx, cpy_PyObject *obj); 29 | 30 | #else 31 | 32 | // Python.h has already been included by the main hpy.h 33 | typedef PyObject cpy_PyObject; 34 | typedef PyMethodDef cpy_PyMethodDef; 35 | typedef PyModuleDef cpy_PyModuleDef; 36 | typedef Py_buffer cpy_Py_buffer; 37 | 38 | #endif /* HPY_ABI_UNIVERSAL */ 39 | 40 | 41 | typedef cpy_PyObject *(*cpy_PyCFunction)(cpy_PyObject *, cpy_PyObject *const *, HPy_ssize_t); 42 | typedef int (*cpy_visitproc)(cpy_PyObject *, void *); 43 | typedef cpy_PyObject *(*cpy_getter)(cpy_PyObject *, void *); 44 | typedef int (*cpy_setter)(cpy_PyObject *, cpy_PyObject *, void *); 45 | typedef void (*cpy_PyCapsule_Destructor)(cpy_PyObject *); 46 | typedef cpy_PyObject *(*cpy_vectorcallfunc)(cpy_PyObject *callable, cpy_PyObject *const *args, 47 | size_t nargsf, cpy_PyObject *kwnames); 48 | 49 | #endif /* HPY_UNIVERSAL_CPY_TYPES_H */ 50 | -------------------------------------------------------------------------------- /hpy/trace/src/trace_internal.h: -------------------------------------------------------------------------------- 1 | /* Internal header for all the files in hpy/debug/src. The public API is in 2 | include/hpy_debug.h 3 | */ 4 | #ifndef HPY_TRACE_INTERNAL_H 5 | #define HPY_TRACE_INTERNAL_H 6 | 7 | #include 8 | #ifdef _WIN32 9 | #include 10 | #else 11 | #include 12 | #endif 13 | #include "hpy.h" 14 | #include "hpy_trace.h" 15 | 16 | 17 | #define HPY_TRACE_MAGIC 0xF00BAA5 18 | 19 | // frequency of nanosecond resolution 20 | #define FREQ_NSEC 1000000000L 21 | 22 | /* === HPyTraceInfo === */ 23 | 24 | #ifdef _WIN32 25 | typedef LARGE_INTEGER _HPyTime_t; 26 | typedef BOOL _HPyClockStatus_t; 27 | #else 28 | typedef struct timespec _HPyTime_t; 29 | typedef int _HPyClockStatus_t; 30 | #endif 31 | 32 | typedef struct { 33 | long magic_number; // used just for sanity checks 34 | HPyContext *uctx; 35 | /* frequency of the used performance counter */ 36 | _HPyTime_t counter_freq; 37 | #ifdef _WIN32 38 | /* to_ns = FREQ_NS / counter_freq */ 39 | int64_t to_ns; 40 | #endif 41 | /* call count of the corresponding HPy API function */ 42 | uint64_t *call_counts; 43 | /* durations spent in the corresponding HPy API function */ 44 | _HPyTime_t *durations; 45 | HPy on_enter_func; 46 | HPy on_exit_func; 47 | } HPyTraceInfo; 48 | 49 | 50 | static inline HPyTraceInfo *get_info(HPyContext *tctx) 51 | { 52 | HPyTraceInfo *info = (HPyTraceInfo*)tctx->_private; 53 | assert(info->magic_number == HPY_TRACE_MAGIC); // sanity check 54 | return info; 55 | } 56 | 57 | /* Get the current value of the monotonic clock. This is a platform-dependent 58 | operation. */ 59 | static inline _HPyClockStatus_t get_monotonic_clock(_HPyTime_t *t) 60 | { 61 | #ifdef _WIN32 62 | return (int)QueryPerformanceCounter(t); 63 | #else 64 | return clock_gettime(CLOCK_MONOTONIC_RAW, t); 65 | #endif 66 | } 67 | 68 | HPyTraceInfo *hpy_trace_on_enter(HPyContext *tctx, int id); 69 | void hpy_trace_on_exit(HPyTraceInfo *info, int id, _HPyClockStatus_t r0, 70 | _HPyClockStatus_t r1, _HPyTime_t *_ts_start, _HPyTime_t *_ts_end); 71 | 72 | #endif /* HPY_TRACE_INTERNAL_H */ 73 | -------------------------------------------------------------------------------- /test/test_importing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .support import HPyTest 3 | 4 | @pytest.fixture(params=['cpython', 'universal', 'hybrid', 'debug']) 5 | def hpy_abi(request): 6 | abi = request.param 7 | yield abi 8 | 9 | 10 | class TestImporting(HPyTest): 11 | 12 | def full_import(self, name, mod_filename): 13 | import importlib 14 | import sys 15 | import os 16 | if name in sys.modules: 17 | raise ValueError( 18 | "Test module {!r} already present in sys.modules".format(name)) 19 | importlib.invalidate_caches() 20 | mod_dir = os.path.dirname(mod_filename) 21 | sys.path.insert(0, mod_dir) 22 | try: 23 | module = importlib.import_module(name) 24 | assert sys.modules[name] is module 25 | finally: 26 | # assert that the module import didn't change the sys.path entry 27 | # that was added above, then remove the entry. 28 | assert sys.path[0] == mod_dir 29 | del sys.path[0] 30 | if name in sys.modules: 31 | del sys.modules[name] 32 | return module 33 | 34 | def test_importing_attributes(self, hpy_abi, tmpdir): 35 | import pytest 36 | if not self.supports_ordinary_make_module_imports(): 37 | pytest.skip() 38 | from hpy.devel.abitag import get_hpy_ext_suffix 39 | mod = self.make_module(""" 40 | @INIT 41 | """, name='mytest') 42 | mod = self.full_import(mod.__name__, mod.__file__) 43 | assert mod.__name__ == 'mytest' 44 | assert mod.__package__ == '' 45 | assert mod.__doc__ == 'some test for hpy' 46 | assert mod.__loader__.name == 'mytest' 47 | assert mod.__spec__.loader is mod.__loader__ 48 | assert mod.__spec__.name == 'mytest' 49 | assert mod.__file__ 50 | 51 | if hpy_abi == 'debug': 52 | hpy_abi = 'universal' 53 | ext_suffix = get_hpy_ext_suffix(hpy_abi) 54 | assert repr(mod) == ''.format( 55 | repr(str(tmpdir.join('mytest' + ext_suffix)))) 56 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_tuplebuilder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "hpy.h" 4 | 5 | #ifndef HPY_ABI_CPYTHON 6 | // for _h2py and _py2h 7 | # include "handles.h" 8 | #endif 9 | 10 | 11 | _HPy_HIDDEN HPyTupleBuilder 12 | ctx_TupleBuilder_New(HPyContext *ctx, HPy_ssize_t size) 13 | { 14 | PyObject *tup = PyTuple_New(size); 15 | if (tup == NULL) { 16 | PyErr_Clear(); /* delay the MemoryError */ 17 | /* note: it's done this way so that the caller doesn't need to 18 | check if HPyTupleBuilder_New() or every HPyTupleBuilder_Set() 19 | raised. If there is a rare error like a MemoryError somewhere, 20 | further calls to the HPyTupleBuilder are ignored. The final 21 | HPyTupleBuilder_Build() will re-raise the MemoryError and so 22 | it's enough for the caller to check at that point. */ 23 | } 24 | return (HPyTupleBuilder){(HPy_ssize_t)tup}; 25 | } 26 | 27 | _HPy_HIDDEN void 28 | ctx_TupleBuilder_Set(HPyContext *ctx, HPyTupleBuilder builder, 29 | HPy_ssize_t index, HPy h_item) 30 | { 31 | PyObject *tup = (PyObject *)builder._tup; 32 | if (tup != NULL) { 33 | PyObject *item = _h2py(h_item); 34 | assert(index >= 0 && index < PyTuple_GET_SIZE(tup)); 35 | assert(PyTuple_GET_ITEM(tup, index) == NULL); 36 | Py_INCREF(item); 37 | PyTuple_SET_ITEM(tup, index, item); 38 | } 39 | } 40 | 41 | _HPy_HIDDEN HPy 42 | ctx_TupleBuilder_Build(HPyContext *ctx, HPyTupleBuilder builder) 43 | { 44 | PyObject *tup = (PyObject *)builder._tup; 45 | if (tup == NULL) { 46 | PyErr_NoMemory(); 47 | return HPy_NULL; 48 | } 49 | builder._tup = 0; 50 | return _py2h(tup); 51 | } 52 | 53 | _HPy_HIDDEN void 54 | ctx_TupleBuilder_Cancel(HPyContext *ctx, HPyTupleBuilder builder) 55 | { 56 | PyObject *tup = (PyObject *)builder._tup; 57 | if (tup == NULL) { 58 | // we don't report the memory error here: the builder 59 | // is being cancelled (so the result of the builder is not being used) 60 | // and likely it's being cancelled during the handling of another error 61 | return; 62 | } 63 | builder._tup = 0; 64 | Py_XDECREF(tup); 65 | } 66 | -------------------------------------------------------------------------------- /hpy/debug/src/include/hpy_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_DEBUG_H 2 | #define HPY_DEBUG_H 3 | 4 | #include "hpy.h" 5 | 6 | /* 7 | This is the main public API for the debug mode, and it's meant to be used 8 | by hpy.universal implementations (including but not limited to the 9 | CPython's version of hpy.universal which is included in this repo). 10 | 11 | The idea is that for every uctx there is a corresponding unique dctx which 12 | wraps it. 13 | 14 | If you call hpy_debug_get_ctx twice on the same uctx, you get the same 15 | result. 16 | 17 | IMPLEMENTATION NOTE: at the moment of writing, the only known user of the 18 | debug mode is CPython's hpy.universal: in that module, the uctx is a 19 | statically allocated singleton, so for simplicity of implementation 20 | currently we do the same inside debug_ctx.c, with a sanity check to ensure 21 | that we don't call hpy_debug_get_ctx with different uctxs. But this is a 22 | limitation of the current implementation and users should not rely on it. It 23 | is likely that we will need to change it in the future, e.g. if we want to 24 | have per-subinterpreter uctxs. 25 | */ 26 | 27 | HPyContext * hpy_debug_get_ctx(HPyContext *uctx); 28 | int hpy_debug_ctx_init(HPyContext *dctx, HPyContext *uctx); 29 | void hpy_debug_set_ctx(HPyContext *dctx); 30 | 31 | // convert between debug and universal handles. These are basically 32 | // the same as DHPy_open and DHPy_unwrap but with a different name 33 | // because this is the public-facing API and DHPy/UHPy are only internal 34 | // implementation details. 35 | HPy hpy_debug_open_handle(HPyContext *dctx, HPy uh); 36 | HPy hpy_debug_unwrap_handle(HPyContext *dctx, HPy dh); 37 | void hpy_debug_close_handle(HPyContext *dctx, HPy dh); 38 | 39 | // this is the HPy init function created by HPy_MODINIT. In CPython's version 40 | // of hpy.universal the code is embedded inside the extension, so we can call 41 | // this function directly instead of dlopen it. This is similar to what 42 | // CPython does for its own built-in modules. But we must use the same 43 | // signature as HPy_MODINIT 44 | 45 | #ifdef ___cplusplus 46 | extern "C" 47 | #endif 48 | HPy_EXPORTED_SYMBOL 49 | HPyModuleDef* HPyInit__debug(); 50 | 51 | #ifdef ___cplusplus 52 | extern "C" 53 | #endif 54 | HPy_EXPORTED_SYMBOL 55 | void HPyInitGlobalContext__debug(HPyContext *ctx); 56 | 57 | #endif /* HPY_DEBUG_H */ 58 | -------------------------------------------------------------------------------- /hpy/devel/abitag.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from distutils import sysconfig 3 | 4 | # NOTE: these must be kept on sync with the equivalent defines in hpy.h 5 | HPY_ABI_VERSION = 0 6 | HPY_ABI_VERSION_MINOR = 0 7 | HPY_ABI_TAG = 'hpy%d' % HPY_ABI_VERSION 8 | 9 | def parse_ext_suffix(ext_suffix=None): 10 | """ 11 | Parse EXT_SUFFIX and return abi_tag, ext. 12 | 13 | - abi_tag is a string representing the CPython ABI tag: e.g. 'cp38', 'cp310d', etc. 14 | - ext is the filename extension, e.g 'so', 'pyd' or 'dylib' 15 | """ 16 | if ext_suffix is None: 17 | ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') 18 | 19 | # ext_suffix is something like this. We want to keep the parts which are 20 | # related to the ABI and remove the parts which are related to the 21 | # platform: 22 | # - linux: '.cpython-310-x86_64-linux-gnu.so' 23 | # - mac: '.cpython-310-darwin.so' 24 | # - win: '.cp310-win_amd64.pyd' 25 | # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' 26 | # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib' 27 | assert ext_suffix[0] == '.' 28 | _, soabi, ext = ext_suffix.split('.') 29 | 30 | # this is very ad-hoc but couldn't find a better way to do it 31 | parts = soabi.split('-') 32 | if parts[0].startswith('cpython'): # cpython on linux, mac 33 | n = 2 34 | elif parts[0].startswith('cp'): # cpython on windows 35 | n = 1 36 | elif parts[0].startswith('pypy'): # pypy 37 | n = 2 38 | elif parts[0].startswith('graalpy'): # graalpy 39 | n = 3 40 | else: # none of the above, keep all parts 41 | n = len(parts) 42 | 43 | abi_tag = '-'.join(parts[:n]) 44 | # on cpython linux/mac, abi is now cpython-310: shorten it ot cp310, like 45 | # on windows and on wheel tags 46 | abi_tag = abi_tag.replace('cpython-', 'cp') 47 | return abi_tag, ext 48 | 49 | 50 | def get_hpy_ext_suffix(hpy_abi, ext_suffix=None): 51 | """ 52 | Return the proper filename extension for the given hpy_abi. 53 | 54 | For example with CPython 3.10 on Linux, it will return: 55 | 56 | - universal ==> .hpy0.so 57 | - hybrid ==> .hpy0-cp310.so 58 | """ 59 | assert hpy_abi in ('cpython', 'universal', 'hybrid') 60 | cpy_abi_tag, ext = parse_ext_suffix(ext_suffix) 61 | if hpy_abi == 'cpython': 62 | return sysconfig.get_config_var('EXT_SUFFIX') 63 | elif hpy_abi == 'universal': 64 | return '.%s.%s' % (HPY_ABI_TAG, ext) 65 | else: 66 | return '.%s-%s.%s' % (HPY_ABI_TAG, cpy_abi_tag, ext) 67 | -------------------------------------------------------------------------------- /docs/examples/snippets/snippets.c: -------------------------------------------------------------------------------- 1 | /* Module with various code snippets used in the docs. 2 | * All code snippets should be put into this file if possible. Notable 3 | * exception are code snippets showing definition of the module or the 4 | * HPyDef array initialization. Remember to also add tests to ../tests.py 5 | */ 6 | #include "hpy.h" 7 | 8 | // ------------------------------------ 9 | // Snippets used in api.rst 10 | 11 | // BEGIN: foo 12 | void foo(HPyContext *ctx) 13 | { 14 | HPy x = HPyLong_FromLong(ctx, 42); 15 | HPy y = HPy_Dup(ctx, x); 16 | /* ... */ 17 | // we need to close x and y independently 18 | HPy_Close(ctx, x); 19 | HPy_Close(ctx, y); 20 | } 21 | // END: foo 22 | 23 | // BEGIN: is_same_object 24 | int is_same_object(HPyContext *ctx, HPy x, HPy y) 25 | { 26 | // return x == y; // compilation error! 27 | return HPy_Is(ctx, x, y); 28 | } 29 | // END: is_same_object 30 | 31 | // dummy entry point so that we can test the snippets: 32 | HPyDef_METH(test_foo_and_is_same_object, "test_foo_and_is_same_object", HPyFunc_VARARGS) 33 | static HPy test_foo_and_is_same_object_impl(HPyContext *ctx, HPy self, 34 | const HPy *args, size_t nargs) 35 | { 36 | foo(ctx); // not much we can test here 37 | return HPyLong_FromLong(ctx, is_same_object(ctx, args[0], args[1])); 38 | } 39 | 40 | // BEGIN: test_leak_stacktrace 41 | HPyDef_METH(test_leak_stacktrace, "test_leak_stacktrace", HPyFunc_NOARGS) 42 | static HPy test_leak_stacktrace_impl(HPyContext *ctx, HPy self) 43 | { 44 | HPy num = HPyLong_FromLong(ctx, 42); 45 | if (HPy_IsNull(num)) { 46 | return HPy_NULL; 47 | } 48 | // No HPy_Close(ctx, num); 49 | return HPy_Dup(ctx, ctx->h_None); 50 | } 51 | // END: test_leak_stacktrace 52 | 53 | // BEGIN: add 54 | HPyDef_METH(add, "add", HPyFunc_VARARGS) 55 | static HPy add_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 56 | { 57 | if (nargs != 2) { 58 | HPyErr_SetString(ctx, ctx->h_TypeError, "expected exactly two args"); 59 | return HPy_NULL; 60 | } 61 | return HPy_Add(ctx, args[0], args[1]); 62 | } 63 | // END: add 64 | 65 | // ------------------------------------ 66 | // Dummy module definition, so that we can test the snippets 67 | 68 | static HPyDef *Methods[] = { 69 | &test_foo_and_is_same_object, 70 | &test_leak_stacktrace, 71 | &add, 72 | NULL, 73 | }; 74 | 75 | static HPyModuleDef snippets = { 76 | .doc = "Various HPy code snippets for the docs", 77 | .size = 0, 78 | .defines = Methods 79 | }; 80 | 81 | HPy_MODINIT(snippets, snippets) 82 | -------------------------------------------------------------------------------- /hpy/tools/autogen/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parse public_api.h and generate various stubs around 3 | """ 4 | import sys 5 | import py 6 | import pycparser 7 | from packaging import version 8 | 9 | if version.parse(pycparser.__version__) < version.parse('2.21'): 10 | raise ImportError('You need pycparser>=2.21 to run autogen') 11 | 12 | from . import generate 13 | from .ctx import (autogen_ctx_h, 14 | autogen_ctx_def_h, 15 | cpython_autogen_ctx_h) 16 | from .trampolines import (autogen_trampolines_h, 17 | cpython_autogen_api_impl_h, 18 | universal_autogen_ctx_impl_h) 19 | from .hpyfunc import autogen_hpyfunc_declare_h 20 | from .hpyfunc import autogen_hpyfunc_trampoline_h 21 | from .hpyfunc import autogen_ctx_call_i 22 | from .hpyfunc import autogen_cpython_hpyfunc_trampoline_h 23 | from .hpyslot import autogen_hpyslot_h 24 | from .debug import (autogen_debug_ctx_init_h, 25 | autogen_debug_wrappers, 26 | autogen_debug_ctx_call_i) 27 | from .trace import (autogen_tracer_ctx_init_h, 28 | autogen_tracer_wrappers, 29 | autogen_trace_func_table_c) 30 | from .pypy import autogen_pypy_txt 31 | from .doc import (autogen_function_index, 32 | autogen_doc_api_mapping) 33 | 34 | DEFAULT_GENERATORS = (autogen_ctx_h, 35 | autogen_ctx_def_h, 36 | cpython_autogen_ctx_h, 37 | autogen_trampolines_h, 38 | cpython_autogen_api_impl_h, 39 | universal_autogen_ctx_impl_h, 40 | autogen_hpyfunc_declare_h, 41 | autogen_hpyfunc_trampoline_h, 42 | autogen_ctx_call_i, 43 | autogen_cpython_hpyfunc_trampoline_h, 44 | autogen_hpyslot_h, 45 | autogen_debug_ctx_init_h, 46 | autogen_debug_wrappers, 47 | autogen_debug_ctx_call_i, 48 | autogen_tracer_ctx_init_h, 49 | autogen_tracer_wrappers, 50 | autogen_trace_func_table_c, 51 | autogen_pypy_txt, 52 | autogen_function_index, 53 | autogen_doc_api_mapping) 54 | 55 | 56 | def main(): 57 | if len(sys.argv) != 2: 58 | print('Usage: python -m hpy.tools.autogen OUTDIR') 59 | sys.exit(1) 60 | outdir = py.path.local(sys.argv[1]) 61 | 62 | generate(DEFAULT_GENERATORS, outdir) 63 | 64 | 65 | if __name__ == '__main__': 66 | main() 67 | -------------------------------------------------------------------------------- /docs/trace-mode.rst: -------------------------------------------------------------------------------- 1 | Trace Mode 2 | ========== 3 | 4 | HPy's trace mode allows you to analyze the usage of the HPy API. The two 5 | fundamental metrics are ``call count`` and ``duration``. As the name already 6 | suggests, ``call count`` tells you how often a certain HPy API function was called 7 | and ``duration`` uses a monotonic clock to measure how much (accumulated) time was 8 | spent in a certain HPy API function. It is further possible to register custom 9 | *on-enter* and *on-exit* Python functions. 10 | 11 | As with the debug mode, the trace mode can be activated at *import time*, so no 12 | recompilation is required. 13 | 14 | 15 | Activating Trace Mode 16 | --------------------- 17 | 18 | Similar to how the 19 | :ref:`debug mode is activated `, use 20 | environment variable ``HPY``. If ``HPY=trace``, then all HPy modules are loaded 21 | with the trace context. Alternatively, it is also possible to specify the mode 22 | per module like this: ``HPY=modA:trace,modB:trace``. 23 | Environment variable ``HPY_LOG`` also works. 24 | 25 | 26 | Using Trace Mode 27 | ---------------- 28 | 29 | The trace mode can be accessed via the shipped module ``hpy.trace``. It provides 30 | following functions: 31 | 32 | * ``get_call_counts()`` returns a dict. The HPy API function names are used as 33 | keys and the corresponding call count is the value. 34 | * ``get_durations()`` also returns a dict similar to ``get_call_counts`` but 35 | the value is the accumulated time spent in the corresponding HPy API 36 | function (in nanoseconds). Note, the used clock does not necessarily have a 37 | nanosecond resolution which means that the least significant digits may not be 38 | accurate. 39 | * ``set_trace_functions(on_enter=None, on_exit=None)`` allows the user to 40 | register custom trace functions. The function provided for ``on_enter`` and 41 | ``on_exit`` functions will be executed before and after and HPy API function 42 | is and was executed, respectively. Passing ``None`` to any of the two 43 | arguments or omitting one will clear the corresponding function. 44 | * ``get_frequency()`` returns the resolution of the used clock to measure the 45 | time in Hertz. For example, a value of ``10000000`` corresponds to 46 | ``10 MHz``. In that case, the two least significant digits of the durations 47 | are inaccurate. 48 | 49 | 50 | Example 51 | ------- 52 | 53 | Following HPy function uses ``HPy_Add``: 54 | 55 | .. literalinclude:: examples/snippets/snippets.c 56 | :start-after: // BEGIN: add 57 | :end-before: // END: add 58 | 59 | When this script is executed in trace mode: 60 | 61 | .. literalinclude:: examples/trace-example.py 62 | :language: python 63 | 64 | The output is ``get_call_counts()["ctx_Add"] == 1``. 65 | -------------------------------------------------------------------------------- /c_test/test_debug_handles.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "acutest.h" // https://github.com/mity/acutest 3 | #include "hpy/debug/src/debug_internal.h" 4 | 5 | static void check_DHQueue(DHQueue *q, HPy_ssize_t size, ...) 6 | { 7 | va_list argp; 8 | va_start(argp, size); 9 | DHQueue_sanity_check(q); 10 | TEST_CHECK(q->size == size); 11 | DHQueueNode *h = q->head; 12 | while(h != NULL) { 13 | DHQueueNode *expected = va_arg(argp, DHQueueNode*); 14 | TEST_CHECK(h == expected); 15 | h = h->next; 16 | } 17 | va_end(argp); 18 | } 19 | 20 | void test_DHQueue_init(void) 21 | { 22 | DHQueue q; 23 | DHQueue_init(&q); 24 | TEST_CHECK(q.head == NULL); 25 | TEST_CHECK(q.tail == NULL); 26 | TEST_CHECK(q.size == 0); 27 | DHQueue_sanity_check(&q); 28 | } 29 | 30 | void test_DHQueue_append(void) 31 | { 32 | DHQueue q; 33 | DHQueueNode h1; 34 | DHQueueNode h2; 35 | DHQueueNode h3; 36 | DHQueue_init(&q); 37 | DHQueue_append(&q, &h1); 38 | DHQueue_append(&q, &h2); 39 | DHQueue_append(&q, &h3); 40 | check_DHQueue(&q, 3, &h1, &h2, &h3); 41 | } 42 | 43 | void test_DHQueue_popfront(void) 44 | { 45 | DHQueue q; 46 | DHQueueNode h1; 47 | DHQueueNode h2; 48 | DHQueueNode h3; 49 | DHQueue_init(&q); 50 | DHQueue_append(&q, &h1); 51 | DHQueue_append(&q, &h2); 52 | DHQueue_append(&q, &h3); 53 | 54 | TEST_CHECK(DHQueue_popfront(&q) == &h1); 55 | check_DHQueue(&q, 2, &h2, &h3); 56 | 57 | TEST_CHECK(DHQueue_popfront(&q) == &h2); 58 | check_DHQueue(&q, 1, &h3); 59 | 60 | TEST_CHECK(DHQueue_popfront(&q) == &h3); 61 | check_DHQueue(&q, 0); 62 | } 63 | 64 | 65 | void test_DHQueue_remove(void) 66 | { 67 | DHQueue q; 68 | DHQueueNode h1; 69 | DHQueueNode h2; 70 | DHQueueNode h3; 71 | DHQueueNode h4; 72 | DHQueue_init(&q); 73 | DHQueue_append(&q, &h1); 74 | DHQueue_append(&q, &h2); 75 | DHQueue_append(&q, &h3); 76 | DHQueue_append(&q, &h4); 77 | 78 | DHQueue_remove(&q, &h3); // try to remove something in the middle 79 | check_DHQueue(&q, 3, &h1, &h2, &h4); 80 | 81 | DHQueue_remove(&q, &h1); // try to remove the head 82 | check_DHQueue(&q, 2, &h2, &h4); 83 | 84 | DHQueue_remove(&q, &h4); // try to remove the tail 85 | check_DHQueue(&q, 1, &h2); 86 | 87 | DHQueue_remove(&q, &h2); // try to remove the only element 88 | check_DHQueue(&q, 0); 89 | } 90 | 91 | #define MYTEST(X) { #X, X } 92 | 93 | TEST_LIST = { 94 | MYTEST(test_DHQueue_init), 95 | MYTEST(test_DHQueue_append), 96 | MYTEST(test_DHQueue_popfront), 97 | MYTEST(test_DHQueue_remove), 98 | { NULL, NULL } 99 | }; 100 | -------------------------------------------------------------------------------- /test/check_py27_compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Some of the files in this repo are used also by PyPy tests, which run on 3 | python2.7. 4 | 5 | This script tries to import all of them: it does not check any behavior, just 6 | that they are importable and thus are not using any py3-specific syntax. 7 | 8 | This script assumes that pathlib and pytest are installed (because the modules 9 | try to import them). 10 | """ 11 | 12 | from __future__ import print_function 13 | import sys 14 | import traceback 15 | import py 16 | 17 | ROOT = py.path.local(__file__).join('..', '..') 18 | TEST_DIRS = [ROOT / 'test', ROOT / 'test' / 'debug'] 19 | 20 | # PyPy does NOT import these files using py2 21 | PY3_ONLY = ['test_support.py', 22 | 'test_handles_invalid.py', 23 | 'test_handles_leak.py', 24 | 'test_builder_invalid.py'] 25 | 26 | def try_import(name): 27 | try: 28 | if isinstance(name, py.path.local): 29 | print('Trying to import %s... ' % ROOT.bestrelpath(name), end='') 30 | name.pyimport() 31 | else: 32 | print('Trying to import %s... ' % name, end='') 33 | __import__(name) 34 | except: 35 | print('ERROR!') 36 | print() 37 | traceback.print_exc(file=sys.stdout) 38 | print() 39 | return False 40 | else: 41 | print('OK') 42 | return True 43 | 44 | def try_import_hpy_devel(): 45 | """ 46 | To import hpy.devel we need to create an empty hpy/__init__.py, because 47 | python2.7 does not support namespace packages. 48 | 49 | Return the number of failed imports. 50 | """ 51 | failed = 0 52 | init_py = ROOT.join('hpy', '__init__.py') 53 | assert init_py.check(exists=False) 54 | try: 55 | init_py.write('') # create an empty __init__.py 56 | if not try_import('hpy.devel'): 57 | failed += 1 58 | finally: 59 | init_py.remove() 60 | return failed 61 | 62 | def try_import_tests(dirs): 63 | failed = 0 64 | for d in dirs: 65 | for t in d.listdir('test_*.py'): 66 | if t.basename in PY3_ONLY: 67 | continue 68 | if not try_import(t): 69 | failed += 1 70 | return failed 71 | 72 | 73 | def main(): 74 | if sys.version_info[:2] != (2, 7): 75 | print('ERROR: this script should be run on top of python 2.7') 76 | sys.exit(1) 77 | 78 | sys.path.insert(0, str(ROOT)) 79 | failed = 0 80 | failed += try_import_hpy_devel() 81 | failed += try_import_tests(TEST_DIRS) 82 | print() 83 | if failed == 0: 84 | print('Everything ok!') 85 | else: 86 | print('%d failed imports :(' % failed) 87 | sys.exit(1) 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. HPy documentation master file, created by 2 | sphinx-quickstart on Thu Apr 2 23:01:08 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | HPy: a better API for Python 7 | =============================== 8 | 9 | HPy provides a new API for extending Python in C. 10 | 11 | There are several advantages to writing C extensions in HPy: 12 | 13 | - **Speed**: it runs much faster on PyPy, GraalPy, and at native speed on CPython 14 | 15 | - **Deployment**: it is possible to compile a single binary which runs unmodified on all 16 | supported Python implementations and versions -- think "stable ABI" on steroids 17 | 18 | - **Simplicity**: it is simpler and more manageable than the ``Python.h`` API, both for 19 | the users and the Pythons implementing it 20 | 21 | - **Debugging**: it provides an improved debugging experience. Debug mode can be turned 22 | on at runtime without the need to recompile the extension or the Python running it. 23 | HPy design is more suitable for automated checks. 24 | 25 | The official `Python/C API `_, 26 | also informally known as ``#include ``, is 27 | specific to the current implementation of CPython: it exposes a lot of 28 | internal details which makes it hard to: 29 | 30 | - implement it for other Python implementations (e.g. PyPy, GraalPy, 31 | Jython, ...) 32 | 33 | - experiment with new approaches inside CPython itself, for example: 34 | 35 | - use a tracing garbage collection instead of reference counting 36 | - remove the global interpreter lock (GIL) to take full advantage of multicore architectures 37 | - use tagged pointers to reduce memory footprint 38 | 39 | Where to go next: 40 | ----------------- 41 | 42 | - Show me the code: 43 | 44 | - :doc:`Quickstart` 45 | - :ref:`Simple documented HPy extension example` 46 | - :doc:`Tutorial: porting Python/C API extension to HPy` 47 | 48 | - Details: 49 | 50 | - :doc:`HPy overview: motivation, goals, current status` 51 | - :doc:`HPy API concepts introduction` 52 | - :doc:`Python/C API to HPy Porting guide` 53 | - :doc:`HPy API reference` 54 | 55 | 56 | Full table of contents: 57 | ----------------------- 58 | 59 | .. toctree:: 60 | :maxdepth: 2 61 | 62 | quickstart 63 | overview 64 | api 65 | porting-guide 66 | porting-example/index 67 | debug-mode 68 | trace-mode 69 | api-reference/index 70 | contributing/index 71 | misc/index 72 | changelog 73 | 74 | 75 | Indices and tables 76 | ================== 77 | 78 | * :ref:`genindex` 79 | * :ref:`modindex` 80 | * :ref:`search` 81 | -------------------------------------------------------------------------------- /hpy/debug/src/memprotect.c: -------------------------------------------------------------------------------- 1 | #include "debug_internal.h" 2 | 3 | // Implements OS dependent abstraction of memory protection 4 | 5 | #ifdef _HPY_DEBUG_MEM_PROTECT_USEMMAP 6 | 7 | #ifndef _WIN32 8 | 9 | // On UNIX systems we use mmap and mprotect 10 | 11 | #include 12 | #include 13 | 14 | void *raw_data_copy(const void* data, HPy_ssize_t size, bool write_protect) { 15 | void* new_ptr; 16 | new_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 17 | if (new_ptr == NULL) 18 | return NULL; 19 | memcpy(new_ptr, data, size); 20 | if (write_protect) { 21 | mprotect(new_ptr, size, PROT_READ); 22 | } 23 | return new_ptr; 24 | } 25 | 26 | void raw_data_protect(void* data, HPy_ssize_t size) { 27 | mprotect(data, size, PROT_NONE); 28 | } 29 | 30 | int raw_data_free(void *data, HPy_ssize_t size) { 31 | return munmap(data, size); 32 | } 33 | 34 | #else 35 | 36 | // On Windows systems we use VirtualAlloc and VirtualProtect 37 | 38 | #include 39 | #include 40 | 41 | void *raw_data_copy(const void* data, HPy_ssize_t size, bool write_protect) { 42 | void* new_ptr; 43 | DWORD old; 44 | new_ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 45 | if (new_ptr == NULL) 46 | return NULL; 47 | memcpy(new_ptr, data, size); 48 | if (write_protect) { 49 | VirtualProtect(new_ptr, size, PAGE_READONLY, &old); 50 | } 51 | return new_ptr; 52 | } 53 | 54 | void raw_data_protect(void* data, HPy_ssize_t size) { 55 | DWORD old; 56 | VirtualProtect(data, size, PAGE_NOACCESS, &old); 57 | } 58 | 59 | int raw_data_free(void *data, HPy_ssize_t size) { 60 | return !VirtualFree(data, 0, MEM_RELEASE); 61 | } 62 | 63 | #endif /* _WIN32 */ 64 | 65 | #else 66 | 67 | // Generic fallback that should work for any OS with decent C compiler: copy 68 | // the memory and then override it with garbage to "protect" it from reading. 69 | 70 | #include 71 | #include 72 | 73 | void *raw_data_copy(const void* data, HPy_ssize_t size, bool write_protect) { 74 | void *new_data = malloc(size); 75 | memcpy(new_data, data, size); 76 | return new_data; 77 | } 78 | 79 | void raw_data_protect(void* data, HPy_ssize_t size) { 80 | // Override the data with some garbage in hope that the program will 81 | // eventually crash or give incorrect result if it reads the garbage 82 | char poison[] = {0xBA, 0xD0, 0xDA, 0x7A}; 83 | for (HPy_ssize_t dataIdx = 0, poisonIdx = 0; dataIdx < size; ++dataIdx) { 84 | ((char*)data)[dataIdx] = poison[poisonIdx]; 85 | poisonIdx = (poisonIdx + 1) % sizeof(poison); 86 | } 87 | } 88 | 89 | int raw_data_free(void *data, HPy_ssize_t size) { 90 | free(data); 91 | return 0; 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_capsule.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | _HPy_HIDDEN HPy 10 | ctx_Capsule_New(HPyContext *ctx, void *pointer, const char *name, 11 | HPyCapsule_Destructor *destructor) 12 | { 13 | PyObject *res; 14 | if (destructor) { 15 | // If a destructor is given, it is not allowed to omit the functions 16 | if ((destructor->cpy_trampoline == NULL || destructor->impl == NULL)) { 17 | PyErr_SetString(PyExc_ValueError, "Invalid HPyCapsule destructor"); 18 | return HPy_NULL; 19 | } 20 | res = PyCapsule_New(pointer, name, destructor->cpy_trampoline); 21 | } else { 22 | res = PyCapsule_New(pointer, name, NULL); 23 | } 24 | return _py2h(res); 25 | } 26 | 27 | _HPy_HIDDEN int 28 | ctx_Capsule_SetDestructor(HPyContext *ctx, HPy h_capsule, 29 | HPyCapsule_Destructor *destructor) 30 | { 31 | if (destructor) { 32 | // If a destructor is given, it is not allowed to omit the functions 33 | if ((destructor->cpy_trampoline == NULL || destructor->impl == NULL)) { 34 | PyErr_SetString(PyExc_ValueError, "Invalid HPyCapsule destructor"); 35 | return -1; 36 | } 37 | return PyCapsule_SetDestructor(_h2py(h_capsule), destructor->cpy_trampoline); 38 | } 39 | return PyCapsule_SetDestructor(_h2py(h_capsule), NULL); 40 | } 41 | 42 | #ifndef HPY_ABI_CPYTHON 43 | _HPy_HIDDEN void * 44 | ctx_Capsule_Get(HPyContext *ctx, HPy capsule, _HPyCapsule_key key, const char *name) 45 | { 46 | switch (key) 47 | { 48 | case HPyCapsule_key_Pointer: 49 | return PyCapsule_GetPointer(_h2py(capsule), name); 50 | case HPyCapsule_key_Name: 51 | return (void *) PyCapsule_GetName(_h2py(capsule)); 52 | case HPyCapsule_key_Context: 53 | return PyCapsule_GetContext(_h2py(capsule)); 54 | case HPyCapsule_key_Destructor: 55 | PyErr_SetString(PyExc_ValueError, "Invalid operation: get HPyCapsule_key_Destructor"); 56 | return NULL; 57 | } 58 | /* unreachable */ 59 | assert(0); 60 | return NULL; 61 | } 62 | 63 | _HPy_HIDDEN int 64 | ctx_Capsule_Set(HPyContext *ctx, HPy capsule, _HPyCapsule_key key, void *value) 65 | { 66 | switch (key) 67 | { 68 | case HPyCapsule_key_Pointer: 69 | return PyCapsule_SetPointer(_h2py(capsule), value); 70 | case HPyCapsule_key_Name: 71 | return PyCapsule_SetName(_h2py(capsule), (const char *) value); 72 | case HPyCapsule_key_Context: 73 | return PyCapsule_SetContext(_h2py(capsule), value); 74 | case HPyCapsule_key_Destructor: 75 | return ctx_Capsule_SetDestructor(ctx, capsule, (HPyCapsule_Destructor *) value); 76 | } 77 | /* unreachable */ 78 | assert(0); 79 | return -1; 80 | } 81 | #endif 82 | -------------------------------------------------------------------------------- /test/test_hpyiter.py: -------------------------------------------------------------------------------- 1 | from .support import HPyTest 2 | 3 | class TestIter(HPyTest): 4 | 5 | def test_Check(self): 6 | mod = self.make_module(""" 7 | HPyDef_METH(f, "f", HPyFunc_O) 8 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 9 | { 10 | if (HPyIter_Check(ctx, arg)) 11 | return HPy_Dup(ctx, ctx->h_True); 12 | return HPy_Dup(ctx, ctx->h_False); 13 | } 14 | @EXPORT(f) 15 | @INIT 16 | """) 17 | 18 | class CustomIterable: 19 | def __init__(self): 20 | self._iter = iter([1, 2, 3]) 21 | 22 | def __iter__(self): 23 | return self._iter 24 | 25 | class CustomIterator: 26 | def __init__(self): 27 | self._iter = iter([1, 2, 3]) 28 | 29 | def __iter__(self): 30 | return self._iter 31 | 32 | def __next__(self): 33 | return next(self._iter) 34 | 35 | assert mod.f(object()) is False 36 | assert mod.f(10) is False 37 | 38 | assert mod.f((1, 2)) is False 39 | assert mod.f(iter((1, 2))) is True 40 | 41 | assert mod.f([]) is False 42 | assert mod.f(iter([])) is True 43 | 44 | assert mod.f('hello') is False 45 | assert mod.f(iter('hello')) is True 46 | 47 | assert mod.f(map(int, ("1", "2"))) is True 48 | assert mod.f(range(1, 10)) is False 49 | 50 | assert mod.f(CustomIterable()) is False 51 | assert mod.f(iter(CustomIterable())) is True 52 | assert mod.f(CustomIterator()) is True 53 | 54 | def test_Next(self): 55 | mod = self.make_module(""" 56 | HPyDef_METH(f, "f", HPyFunc_O) 57 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 58 | { 59 | HPy result = HPyIter_Next(ctx, arg); 60 | int is_null = HPy_IsNull(result); 61 | 62 | if (is_null && HPyErr_Occurred(ctx)) 63 | return HPy_NULL; 64 | if (is_null) 65 | return HPyErr_SetObject(ctx, ctx->h_StopIteration, ctx->h_None); 66 | return result; 67 | } 68 | @EXPORT(f) 69 | @INIT 70 | """) 71 | 72 | class CustomIterator: 73 | def __init__(self): 74 | self._iter = iter(["a", "b", "c"]) 75 | 76 | def __iter__(self): 77 | return self._iter 78 | 79 | def __next__(self): 80 | return next(self._iter) 81 | 82 | assert mod.f(iter([3, 2, 1])) == 3 83 | assert mod.f((i for i in range(1, 10))) == 1 84 | assert mod.f(CustomIterator()) == "a" 85 | 86 | import pytest 87 | with pytest.raises(StopIteration): 88 | assert mod.f(iter([])) 89 | 90 | 91 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/cpython/autogen_ctx.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | DO NOT EDIT THIS FILE! 4 | 5 | This file is automatically generated by hpy.tools.autogen.ctx.cpython_autogen_ctx_h 6 | See also hpy.tools.autogen and hpy/tools/public_api.h 7 | 8 | Run this to regenerate: 9 | make autogen 10 | 11 | */ 12 | 13 | struct _HPyContext_s { 14 | const char *name; 15 | int abi_version; 16 | HPy h_None; 17 | HPy h_True; 18 | HPy h_False; 19 | HPy h_NotImplemented; 20 | HPy h_Ellipsis; 21 | HPy h_BaseException; 22 | HPy h_Exception; 23 | HPy h_StopAsyncIteration; 24 | HPy h_StopIteration; 25 | HPy h_GeneratorExit; 26 | HPy h_ArithmeticError; 27 | HPy h_LookupError; 28 | HPy h_AssertionError; 29 | HPy h_AttributeError; 30 | HPy h_BufferError; 31 | HPy h_EOFError; 32 | HPy h_FloatingPointError; 33 | HPy h_OSError; 34 | HPy h_ImportError; 35 | HPy h_ModuleNotFoundError; 36 | HPy h_IndexError; 37 | HPy h_KeyError; 38 | HPy h_KeyboardInterrupt; 39 | HPy h_MemoryError; 40 | HPy h_NameError; 41 | HPy h_OverflowError; 42 | HPy h_RuntimeError; 43 | HPy h_RecursionError; 44 | HPy h_NotImplementedError; 45 | HPy h_SyntaxError; 46 | HPy h_IndentationError; 47 | HPy h_TabError; 48 | HPy h_ReferenceError; 49 | HPy h_SystemError; 50 | HPy h_SystemExit; 51 | HPy h_TypeError; 52 | HPy h_UnboundLocalError; 53 | HPy h_UnicodeError; 54 | HPy h_UnicodeEncodeError; 55 | HPy h_UnicodeDecodeError; 56 | HPy h_UnicodeTranslateError; 57 | HPy h_ValueError; 58 | HPy h_ZeroDivisionError; 59 | HPy h_BlockingIOError; 60 | HPy h_BrokenPipeError; 61 | HPy h_ChildProcessError; 62 | HPy h_ConnectionError; 63 | HPy h_ConnectionAbortedError; 64 | HPy h_ConnectionRefusedError; 65 | HPy h_ConnectionResetError; 66 | HPy h_FileExistsError; 67 | HPy h_FileNotFoundError; 68 | HPy h_InterruptedError; 69 | HPy h_IsADirectoryError; 70 | HPy h_NotADirectoryError; 71 | HPy h_PermissionError; 72 | HPy h_ProcessLookupError; 73 | HPy h_TimeoutError; 74 | HPy h_Warning; 75 | HPy h_UserWarning; 76 | HPy h_DeprecationWarning; 77 | HPy h_PendingDeprecationWarning; 78 | HPy h_SyntaxWarning; 79 | HPy h_RuntimeWarning; 80 | HPy h_FutureWarning; 81 | HPy h_ImportWarning; 82 | HPy h_UnicodeWarning; 83 | HPy h_BytesWarning; 84 | HPy h_ResourceWarning; 85 | HPy h_BaseObjectType; 86 | HPy h_TypeType; 87 | HPy h_BoolType; 88 | HPy h_LongType; 89 | HPy h_FloatType; 90 | HPy h_UnicodeType; 91 | HPy h_TupleType; 92 | HPy h_ListType; 93 | HPy h_ComplexType; 94 | HPy h_BytesType; 95 | HPy h_MemoryViewType; 96 | HPy h_CapsuleType; 97 | HPy h_SliceType; 98 | HPy h_Builtins; 99 | HPy h_DictType; 100 | }; 101 | -------------------------------------------------------------------------------- /test/test_legacy_forbidden.py: -------------------------------------------------------------------------------- 1 | """ 2 | In this file we check that if we use legacy features in universal mode, we 3 | get the expected compile time errors 4 | """ 5 | 6 | from .support import HPyTest, make_hpy_abi_fixture, ONLY_LINUX 7 | 8 | # this is not strictly correct, we should check whether the actual compiler 9 | # is GCC. But for the CI and most cases, it's enough to assume that if we are 10 | # on linux we are using GCC. 11 | # 12 | # We need this because some of the nice compilation errors (such as the ones 13 | # causes by _HPY_LEGACY) are triggered only by gcc. Would be nice to have them 14 | # also for other compilers 15 | ONLY_GCC = ONLY_LINUX 16 | 17 | 18 | class TestLegacyForbidden(HPyTest): 19 | 20 | hpy_abi = make_hpy_abi_fixture(['universal'], class_fixture=True) 21 | 22 | LEGACY_ERROR = "Cannot use legacy functions when targeting the HPy Universal ABI" 23 | 24 | def test_expect_make_error(self): 25 | src = """ 26 | #error "this is a compile time error" 27 | """ 28 | self.expect_make_error(src, "this is a compile time error") 29 | 30 | def test_Python_h_forbidden(self, capfd): 31 | src = """ 32 | #include 33 | @INIT 34 | """ 35 | self.expect_make_error(src, 36 | "It is forbidden to #include " 37 | "when targeting the HPy Universal ABI") 38 | 39 | @ONLY_GCC 40 | def test_HPy_AsPyObject(self, capfd): 41 | # NOTE: in this test we don't include Python.h. We want to test that 42 | # we get a nice compile-time error by just calling HPy_AsPyObject. 43 | # that's why we use "cpy_PyObject" (which is available because defined 44 | # by hpy.h) 45 | src = """ 46 | HPyDef_METH(f, "f", HPyFunc_NOARGS) 47 | static HPy f_impl(HPyContext *ctx, HPy self) 48 | { 49 | cpy_PyObject *pyobj = HPy_AsPyObject(ctx, self); 50 | (void)pyobj; // silence the warning about unused variable 51 | return HPy_NULL; 52 | } 53 | @EXPORT(f) 54 | @INIT 55 | """ 56 | self.expect_make_error(src, self.LEGACY_ERROR) 57 | 58 | @ONLY_GCC 59 | def test_HPy_FromPyObject(self, capfd): 60 | # NOTE: in this test we don't include Python.h. We want to test that 61 | # we get a nice compile-time error by just calling HPy_AsPyObject. 62 | # that's why we use "cpy_PyObject" (which is available because defined 63 | # by hpy.h) 64 | src = """ 65 | HPyDef_METH(f, "f", HPyFunc_NOARGS) 66 | static HPy f_impl(HPyContext *ctx, HPy self) 67 | { 68 | cpy_PyObject *pyobj = NULL; 69 | return HPy_FromPyObject(ctx, pyobj); 70 | } 71 | @EXPORT(f) 72 | @INIT 73 | """ 74 | self.expect_make_error(src, self.LEGACY_ERROR) 75 | -------------------------------------------------------------------------------- /hpy/debug/src/stacktrace.c: -------------------------------------------------------------------------------- 1 | #include "debug_internal.h" 2 | 3 | #if ((__linux__ && __GNU_LIBRARY__) || __APPLE__) 4 | 5 | // Basic implementation that uses backtrace from glibc 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static inline int max_s(size_t a, size_t b) { 12 | return a > b ? a : b; 13 | } 14 | 15 | void create_stacktrace(char **target, HPy_ssize_t max_frames_count) { 16 | const size_t skip_frames = 2; 17 | size_t max_stack_size = (size_t) max_frames_count; 18 | void* stack = calloc(sizeof(void*), max_stack_size); 19 | if (stack == NULL) { 20 | *target = NULL; 21 | return; 22 | } 23 | 24 | size_t stack_size = backtrace(stack, max_stack_size); 25 | if (stack_size <= skip_frames) { 26 | *target = NULL; 27 | free(stack); 28 | return; 29 | } 30 | 31 | char** symbols = backtrace_symbols(stack, stack_size); 32 | if (symbols == NULL) { 33 | *target = NULL; 34 | free(stack); 35 | return; 36 | } 37 | 38 | size_t buffer_size = 1024; 39 | size_t buffer_index = 0; 40 | char *buffer = malloc(buffer_size); 41 | if (buffer == NULL) { 42 | *target = NULL; 43 | free(symbols); 44 | free(stack); 45 | return; 46 | } 47 | 48 | size_t i; 49 | for (i = skip_frames; i < stack_size; ++i) { 50 | size_t current_len = strlen(symbols[i]); 51 | size_t required_buffer_size = buffer_index + current_len + 1; 52 | if (required_buffer_size > buffer_size) { 53 | buffer_size = max_s(buffer_size * 2, required_buffer_size); 54 | char *new_buffer = realloc(buffer, buffer_size); 55 | if (new_buffer == NULL) { 56 | // allocation failed, we can still provide at least part of 57 | // the stack trace that is currently in the buffer 58 | break; 59 | } 60 | buffer = new_buffer; 61 | } 62 | memcpy(buffer + buffer_index, symbols[i], current_len); 63 | buffer[buffer_index + current_len] = '\n'; 64 | buffer_index = required_buffer_size; 65 | } 66 | 67 | // override the last '\n' to '\0' 68 | assert(stack_size - skip_frames > 0); 69 | assert(buffer[buffer_index - 1] == '\n'); 70 | buffer[buffer_index - 1] = '\0'; 71 | char *shorter_buffer = realloc(buffer, buffer_index); 72 | if (shorter_buffer != NULL) { 73 | buffer = shorter_buffer; 74 | } 75 | *target = buffer; 76 | 77 | free(symbols); 78 | free(stack); 79 | } 80 | 81 | #else 82 | 83 | #include 84 | 85 | void create_stacktrace(char **target, HPy_ssize_t max_frames_count) { 86 | const char msg[] = 87 | "Current HPy build does not support getting C stack traces.\n" 88 | "At the moment this is only supported on Linux with glibc" 89 | " and macOS."; 90 | *target = malloc(sizeof(msg)); 91 | memcpy(*target, msg, sizeof(msg)); 92 | } 93 | 94 | #endif -------------------------------------------------------------------------------- /test/test_hpytuple.py: -------------------------------------------------------------------------------- 1 | from .support import HPyTest 2 | 3 | class TestTuple(HPyTest): 4 | 5 | def test_Check(self): 6 | mod = self.make_module(""" 7 | HPyDef_METH(f, "f", HPyFunc_O) 8 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 9 | { 10 | if (HPyTuple_Check(ctx, arg)) 11 | return HPy_Dup(ctx, ctx->h_True); 12 | return HPy_Dup(ctx, ctx->h_False); 13 | } 14 | @EXPORT(f) 15 | @INIT 16 | """) 17 | class MyTuple(tuple): 18 | pass 19 | 20 | assert mod.f(()) is True 21 | assert mod.f([]) is False 22 | assert mod.f(MyTuple()) is True 23 | 24 | def test_FromArray(self): 25 | mod = self.make_module(""" 26 | HPyDef_METH(f, "f", HPyFunc_O) 27 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 28 | { 29 | HPy x = HPyLong_FromLong(ctx, 42); 30 | if (HPy_IsNull(x)) 31 | return HPy_NULL; 32 | HPy items[] = {self, arg, x}; 33 | HPy res = HPyTuple_FromArray(ctx, items, 3); 34 | HPy_Close(ctx, x); 35 | return res; 36 | } 37 | @EXPORT(f) 38 | @INIT 39 | """) 40 | assert mod.f('hello') == (mod, 'hello', 42) 41 | 42 | def test_Pack(self): 43 | mod = self.make_module(""" 44 | HPyDef_METH(f, "f", HPyFunc_O) 45 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 46 | { 47 | HPy x = HPyLong_FromLong(ctx, 42); 48 | if (HPy_IsNull(x)) 49 | return HPy_NULL; 50 | HPy result = HPyTuple_Pack(ctx, 3, self, arg, x); 51 | HPy_Close(ctx, x); 52 | return result; 53 | } 54 | @EXPORT(f) 55 | @INIT 56 | """) 57 | assert mod.f('hello') == (mod, 'hello', 42) 58 | 59 | def test_TupleBuilder(self): 60 | mod = self.make_module(""" 61 | HPyDef_METH(f, "f", HPyFunc_O) 62 | static HPy f_impl(HPyContext *ctx, HPy h_self, HPy h_arg) 63 | { 64 | HPyTupleBuilder builder = HPyTupleBuilder_New(ctx, 3); 65 | HPyTupleBuilder_Set(ctx, builder, 0, h_arg); 66 | HPyTupleBuilder_Set(ctx, builder, 1, ctx->h_True); 67 | HPy h_num = HPyLong_FromLong(ctx, -42); 68 | if (HPy_IsNull(h_num)) 69 | { 70 | HPyTupleBuilder_Cancel(ctx, builder); 71 | return HPy_NULL; 72 | } 73 | HPyTupleBuilder_Set(ctx, builder, 2, h_num); 74 | HPy_Close(ctx, h_num); 75 | HPy h_tuple = HPyTupleBuilder_Build(ctx, builder); 76 | return h_tuple; 77 | } 78 | @EXPORT(f) 79 | @INIT 80 | """) 81 | assert mod.f("xy") == ("xy", True, -42) 82 | -------------------------------------------------------------------------------- /docs/examples/hpytype-example/simple_type.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // BEGIN: PointObject 4 | typedef struct { 5 | long x; 6 | long y; 7 | } PointObject; 8 | HPyType_HELPERS(PointObject) 9 | // END: PointObject 10 | 11 | // BEGIN: members 12 | HPyDef_MEMBER(Point_x, "x", HPyMember_LONG, offsetof(PointObject, x)) 13 | HPyDef_MEMBER(Point_y, "y", HPyMember_LONG, offsetof(PointObject, y)) 14 | // END: members 15 | 16 | // BEGIN: methods 17 | HPyDef_METH(Point_foo, "foo", HPyFunc_NOARGS) 18 | static HPy Point_foo_impl(HPyContext *ctx, HPy self) 19 | { 20 | PointObject *point = PointObject_AsStruct(ctx, self); 21 | return HPyLong_FromLong(ctx, point->x * 10 + point->y); 22 | } 23 | // END: methods 24 | 25 | // BEGIN: getset 26 | HPyDef_GETSET(Point_z, "z", .closure=(void *)1000) 27 | static HPy Point_z_get(HPyContext *ctx, HPy self, void *closure) 28 | { 29 | PointObject *point = PointObject_AsStruct(ctx, self); 30 | return HPyLong_FromLong(ctx, point->x*10 + point->y + (long)(HPy_ssize_t)closure); 31 | } 32 | 33 | static int Point_z_set(HPyContext *ctx, HPy self, HPy value, void *closure) 34 | { 35 | PointObject *point = PointObject_AsStruct(ctx, self); 36 | long current = point->x*10 + point->y + (long)(HPy_ssize_t)closure; 37 | long target = HPyLong_AsLong(ctx, value); // assume no exception 38 | point->y += target - current; 39 | return 0; 40 | } 41 | // END: getset 42 | 43 | // BEGIN: slots 44 | HPyDef_SLOT(Point_new, HPy_tp_new) 45 | static HPy Point_new_impl(HPyContext *ctx, HPy cls, const HPy *args, 46 | HPy_ssize_t nargs, HPy kw) 47 | { 48 | long x, y; 49 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &x, &y)) 50 | return HPy_NULL; 51 | PointObject *point; 52 | HPy h_point = HPy_New(ctx, cls, &point); 53 | if (HPy_IsNull(h_point)) 54 | return HPy_NULL; 55 | point->x = x; 56 | point->y = y; 57 | return h_point; 58 | } 59 | // END: slots 60 | 61 | // BEGIN: defines 62 | static HPyDef *Point_defines[] = { 63 | &Point_x, 64 | &Point_y, 65 | &Point_z, 66 | &Point_new, 67 | &Point_foo, 68 | NULL 69 | }; 70 | // END: defines 71 | 72 | // BEGIN: spec 73 | static HPyType_Spec Point_spec = { 74 | .name = "simple_type.Point", 75 | .basicsize = sizeof(PointObject), 76 | .builtin_shape = PointObject_SHAPE, 77 | .defines = Point_defines 78 | }; 79 | // END: spec 80 | 81 | // BEGIN: add_type 82 | HPyDef_SLOT(simple_exec, HPy_mod_exec) 83 | static int simple_exec_impl(HPyContext *ctx, HPy m) { 84 | if (!HPyHelpers_AddType(ctx, m, "Point", &Point_spec, NULL)) { 85 | return -1; 86 | } 87 | return 0; // success 88 | } 89 | 90 | static HPyDef *mod_defines[] = { 91 | &simple_exec, // 'simple_exec' is generated by the HPyDef_SLOT macro 92 | NULL, 93 | }; 94 | 95 | static HPyModuleDef moduledef = { 96 | .defines = mod_defines, 97 | // ... 98 | // END: add_type 99 | .doc = "A simple HPy type", 100 | }; 101 | 102 | HPy_MODINIT(simple_type, moduledef) -------------------------------------------------------------------------------- /hpy/devel/include/hpy/universal/misc_trampolines.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_MISC_TRAMPOLINES_H 2 | #define HPY_MISC_TRAMPOLINES_H 3 | 4 | static inline HPy _HPy_New(HPyContext *ctx, HPy h_type, void **data) { 5 | /* Performance hack: the autogenerated version of this trampoline would 6 | simply forward data to ctx_New. 7 | 8 | Suppose you call HPy_New this way: 9 | PointObject *point; 10 | HPy h = HPy_New(ctx, cls, &point); 11 | 12 | If you pass "data" to ctx->New, the C compiler must assume that anybody 13 | could write a different value at any time into this local variable 14 | because a pointer to it escaped. With this hack, it's no longer the 15 | case: what escaped is the address of data_result instead and that local 16 | variable disappears since this function is likely inlined. 17 | 18 | See https://github.com/pyhandle/hpy/pull/22#pullrequestreview-413365845 19 | */ 20 | void *data_result; 21 | HPy h = ctx->ctx_New(ctx, h_type, &data_result); 22 | *data = data_result; 23 | return h; 24 | } 25 | 26 | static inline _HPy_NO_RETURN void 27 | HPy_FatalError(HPyContext *ctx, const char *message) { 28 | ctx->ctx_FatalError(ctx, message); 29 | /* note: the following abort() is unreachable, but needed because the 30 | _HPy_NO_RETURN doesn't seem to be sufficient. I think that what 31 | occurs is that this function is inlined, after which gcc forgets 32 | that it couldn't return. Having abort() inlined fixes that. */ 33 | abort(); 34 | } 35 | 36 | static inline void * 37 | HPyCapsule_GetPointer(HPyContext *ctx, HPy capsule, const char *name) 38 | { 39 | return ctx->ctx_Capsule_Get( 40 | ctx, capsule, HPyCapsule_key_Pointer, name); 41 | } 42 | 43 | static inline const char * 44 | HPyCapsule_GetName(HPyContext *ctx, HPy capsule) 45 | { 46 | return (const char *) ctx->ctx_Capsule_Get( 47 | ctx, capsule, HPyCapsule_key_Name, NULL); 48 | } 49 | 50 | static inline void * 51 | HPyCapsule_GetContext(HPyContext *ctx, HPy capsule) 52 | { 53 | return ctx->ctx_Capsule_Get( 54 | ctx, capsule, HPyCapsule_key_Context, NULL); 55 | } 56 | 57 | static inline int 58 | HPyCapsule_SetPointer(HPyContext *ctx, HPy capsule, void *pointer) 59 | { 60 | return ctx->ctx_Capsule_Set( 61 | ctx, capsule, HPyCapsule_key_Pointer, pointer); 62 | } 63 | 64 | static inline int 65 | HPyCapsule_SetName(HPyContext *ctx, HPy capsule, const char *name) 66 | { 67 | return ctx->ctx_Capsule_Set( 68 | ctx, capsule, HPyCapsule_key_Name, (void *) name); 69 | } 70 | 71 | static inline int 72 | HPyCapsule_SetContext(HPyContext *ctx, HPy capsule, void *context) 73 | { 74 | return ctx->ctx_Capsule_Set( 75 | ctx, capsule, HPyCapsule_key_Context, context); 76 | } 77 | 78 | static inline int 79 | HPyCapsule_SetDestructor(HPyContext *ctx, HPy capsule, 80 | HPyCapsule_Destructor *destructor) 81 | { 82 | return ctx->ctx_Capsule_Set( 83 | ctx, capsule, HPyCapsule_key_Destructor, (void *) destructor); 84 | } 85 | 86 | #endif /* HPY_MISC_TRAMPOLINES_H */ 87 | -------------------------------------------------------------------------------- /docs/examples/hpytype-example/builtin_type.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // BEGIN: spec_Dummy 5 | static HPyType_Spec Dummy_spec = { 6 | .name = "builtin_type.Dummy", 7 | .basicsize = 0 8 | }; 9 | 10 | // END: spec_Dummy 11 | static void make_Dummy(HPyContext *ctx, HPy module) 12 | { 13 | // BEGIN: add_Dummy 14 | HPyType_SpecParam param[] = { 15 | { HPyType_SpecParam_Base, ctx->h_UnicodeType }, 16 | { (HPyType_SpecParam_Kind)0 } 17 | }; 18 | if (!HPyHelpers_AddType(ctx, module, "Dummy", &Dummy_spec, param)) 19 | return; 20 | // END: add_Dummy 21 | } 22 | 23 | // BEGIN: LanguageObject 24 | typedef struct { 25 | char *language; 26 | } LanguageObject; 27 | HPyType_HELPERS(LanguageObject, HPyType_BuiltinShape_Unicode) 28 | // END: LanguageObject 29 | 30 | HPyDef_GETSET(Language_lang, "lang") 31 | static HPy Language_lang_get(HPyContext *ctx, HPy self, void *closure) 32 | { 33 | LanguageObject *data = LanguageObject_AsStruct(ctx, self); 34 | return HPyUnicode_FromString(ctx, data->language); 35 | } 36 | static int Language_lang_set(HPyContext *ctx, HPy self, HPy value, void *closure) 37 | { 38 | LanguageObject *data = LanguageObject_AsStruct(ctx, self); 39 | HPy_ssize_t size; 40 | const char *s = HPyUnicode_AsUTF8AndSize(ctx, value, &size); 41 | if (s == NULL) 42 | return -1; 43 | data->language = (char *)calloc(size+1, sizeof(char)); 44 | strncpy(data->language, s, size); 45 | return 0; 46 | } 47 | 48 | HPyDef_SLOT(Language_destroy, HPy_tp_destroy) 49 | static void Language_destroy_impl(void *data) 50 | { 51 | LanguageObject *ldata = (LanguageObject *)data; 52 | if (ldata->language) 53 | free(ldata->language); 54 | } 55 | 56 | HPyDef *Language_defines[] = { 57 | &Language_lang, 58 | &Language_destroy, 59 | NULL 60 | }; 61 | 62 | 63 | // BEGIN: spec_Language 64 | static HPyType_Spec Language_spec = { 65 | .name = "builtin_type.Language", 66 | .basicsize = sizeof(LanguageObject), 67 | .builtin_shape = SHAPE(LanguageObject), 68 | .defines = Language_defines 69 | }; 70 | // END: spec_Language 71 | 72 | static void make_Language(HPyContext *ctx, HPy module) 73 | { 74 | // BEGIN: add_Language 75 | HPyType_SpecParam param[] = { 76 | { HPyType_SpecParam_Base, ctx->h_UnicodeType }, 77 | { (HPyType_SpecParam_Kind)0 } 78 | }; 79 | if (!HPyHelpers_AddType(ctx, module, "Language", &Language_spec, param)) 80 | return; 81 | // END: add_Language 82 | } 83 | 84 | HPyDef_SLOT(simple_exec, HPy_mod_exec) 85 | static int simple_exec_impl(HPyContext *ctx, HPy m) { 86 | make_Dummy(ctx, m); 87 | if (HPyErr_Occurred(ctx)) 88 | return -1; 89 | 90 | make_Language(ctx, m); 91 | if (HPyErr_Occurred(ctx)) 92 | return -1; 93 | 94 | return 0; // success 95 | } 96 | 97 | static HPyDef *mod_defines[] = { 98 | &simple_exec, // 'simple_exec' is generated by the HPyDef_SLOT macro 99 | NULL, 100 | }; 101 | 102 | static HPyModuleDef moduledef = { 103 | .defines = mod_defines 104 | }; 105 | 106 | HPy_MODINIT(builtin_type, moduledef) 107 | -------------------------------------------------------------------------------- /test/test_capsule_legacy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .support import HPyTest, make_hpy_abi_fixture 3 | from .test_capsule import CapsuleTemplate 4 | 5 | hpy_abi = make_hpy_abi_fixture('with hybrid') 6 | 7 | class TestHPyCapsuleLegacy(HPyTest): 8 | 9 | ExtensionTemplate = CapsuleTemplate 10 | 11 | def test_legacy_capsule_compat(self): 12 | import pytest 13 | mod = self.make_module(""" 14 | @DEFINE_strdup 15 | 16 | #include 17 | #include 18 | 19 | static int dummy = 123; 20 | 21 | static void legacy_destructor(PyObject *capsule) 22 | { 23 | /* We need to use C lib 'free' because the string was 24 | created with 'strdup0'. */ 25 | free((void *) PyCapsule_GetName(capsule)); 26 | } 27 | 28 | HPyDef_METH(Create_pycapsule, "create_pycapsule", HPyFunc_O) 29 | static HPy Create_pycapsule_impl(HPyContext *ctx, HPy self, HPy arg) 30 | { 31 | HPy_ssize_t n; 32 | const char *name = HPyUnicode_AsUTF8AndSize(ctx, arg, &n); 33 | char *name_copy = strdup0(name); 34 | if (name_copy == NULL) { 35 | HPyErr_SetString(ctx, ctx->h_MemoryError, "out of memory"); 36 | return HPy_NULL; 37 | } 38 | PyObject *legacy_caps = PyCapsule_New(&dummy, (const char *) name_copy, 39 | legacy_destructor); 40 | HPy res = HPy_FromPyObject(ctx, legacy_caps); 41 | Py_DECREF(legacy_caps); 42 | return res; 43 | } 44 | 45 | HPyDef_METH(Capsule_get, "get", HPyFunc_O) 46 | static HPy Capsule_get_impl(HPyContext *ctx, HPy self, HPy arg) 47 | { 48 | HPy res = HPy_NULL; 49 | HPy h_value = HPy_NULL; 50 | int *ptr = NULL; 51 | 52 | const char *name = HPyCapsule_GetName(ctx, arg); 53 | if (name == NULL && HPyErr_Occurred(ctx)) { 54 | return HPy_NULL; 55 | } 56 | HPy h_name = HPyUnicode_FromString(ctx, name); 57 | if (HPy_IsNull(h_name)) { 58 | goto finish; 59 | } 60 | 61 | ptr = (int *) HPyCapsule_GetPointer(ctx, arg, name); 62 | if (ptr == NULL && HPyErr_Occurred(ctx)) { 63 | goto finish; 64 | } 65 | 66 | h_value = HPyLong_FromLong(ctx, *ptr); 67 | if (HPy_IsNull(h_value)) { 68 | goto finish; 69 | } 70 | 71 | res = HPyTuple_Pack(ctx, 2, h_name, h_value); 72 | 73 | finish: 74 | HPy_Close(ctx, h_name); 75 | HPy_Close(ctx, h_value); 76 | return res; 77 | } 78 | 79 | @EXPORT(Create_pycapsule) 80 | @EXPORT(Capsule_get) 81 | 82 | @INIT 83 | """) 84 | name = "legacy_capsule" 85 | p = mod.create_pycapsule(name) 86 | assert mod.get(p) == (name, 123) 87 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/macros.h: -------------------------------------------------------------------------------- 1 | /* We define HPy_New as a macro around _HPy_New to suppress a 2 | warning. Usually, we expected it to be called this way: 3 | 4 | PointObject *p; 5 | HPy h = HPy_New(ctx, cls, &p); 6 | 7 | If we call _HPy_New directly, we get a warning because we are implicitly 8 | casting a PointObject** into a void**. The following macro explicitly 9 | casts the third argument to a void**. 10 | */ 11 | 12 | #define HPy_New(ctx, cls, data) (_HPy_New( \ 13 | (ctx), \ 14 | (cls), \ 15 | ((void**)data) \ 16 | )) 17 | 18 | /* Rich comparison opcodes */ 19 | typedef enum { 20 | HPy_LT = 0, 21 | HPy_LE = 1, 22 | HPy_EQ = 2, 23 | HPy_NE = 3, 24 | HPy_GT = 4, 25 | HPy_GE = 5, 26 | } HPy_RichCmpOp; 27 | 28 | // this needs to be a macro because val1 and val2 can be of arbitrary types 29 | #define HPy_RETURN_RICHCOMPARE(ctx, val1, val2, op) \ 30 | do { \ 31 | bool result; \ 32 | switch (op) { \ 33 | case HPy_EQ: result = ((val1) == (val2)); break; \ 34 | case HPy_NE: result = ((val1) != (val2)); break; \ 35 | case HPy_LT: result = ((val1) < (val2)); break; \ 36 | case HPy_GT: result = ((val1) > (val2)); break; \ 37 | case HPy_LE: result = ((val1) <= (val2)); break; \ 38 | case HPy_GE: result = ((val1) >= (val2)); break; \ 39 | default: \ 40 | HPy_FatalError(ctx, "Invalid value for HPy_RichCmpOp"); \ 41 | } \ 42 | if (result) \ 43 | return HPy_Dup(ctx, ctx->h_True); \ 44 | return HPy_Dup(ctx, ctx->h_False); \ 45 | } while (0) 46 | 47 | 48 | #if !defined(SIZEOF_PID_T) || SIZEOF_PID_T == SIZEOF_INT 49 | #define _HPy_PARSE_PID "i" 50 | #define HPyLong_FromPid HPyLong_FromLong 51 | #define HPyLong_AsPid HPyLong_AsLong 52 | #elif SIZEOF_PID_T == SIZEOF_LONG 53 | #define _HPy_PARSE_PID "l" 54 | #define HPyLong_FromPid HPyLong_FromLong 55 | #define HPyLong_AsPid HPyLong_AsLong 56 | #elif defined(SIZEOF_LONG_LONG) && SIZEOF_PID_T == SIZEOF_LONG_LONG 57 | #define _HPy_PARSE_PID "L" 58 | #define HPyLong_FromPid HPyLong_FromLongLong 59 | #define HPyLong_AsPid HPyLong_AsLongLong 60 | #else 61 | #error "sizeof(pid_t) is neither sizeof(int), sizeof(long) or sizeof(long long)" 62 | #endif /* SIZEOF_PID_T */ 63 | 64 | #define HPy_BEGIN_LEAVE_PYTHON(context) { \ 65 | HPyThreadState _token; \ 66 | _token = HPy_LeavePythonExecution(context); 67 | 68 | #define HPy_END_LEAVE_PYTHON(context) \ 69 | HPy_ReenterPythonExecution(context, _token); \ 70 | } 71 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_object.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | 10 | _HPy_HIDDEN void 11 | ctx_Dump(HPyContext *ctx, HPy h) 12 | { 13 | // just use _PyObject_Dump for now, but we might want to add more info 14 | // about the handle itself in the future. 15 | _PyObject_Dump(_h2py(h)); 16 | } 17 | 18 | _HPy_HIDDEN HPy 19 | ctx_Type(HPyContext *ctx, HPy obj) 20 | { 21 | PyTypeObject *tp = Py_TYPE(_h2py(obj)); 22 | Py_INCREF(tp); 23 | return _py2h((PyObject *)tp); 24 | } 25 | 26 | /* NOTE: In contrast to CPython, HPy has to check that 'h_type' is a type. This 27 | is not necessary on CPython because it requires C type 'PyTypeObject *' but 28 | here we can only receive an HPy handle. Appropriate checking of the argument 29 | will be done in the debug mode. 30 | */ 31 | _HPy_HIDDEN int 32 | ctx_TypeCheck(HPyContext *ctx, HPy h_obj, HPy h_type) 33 | { 34 | return PyObject_TypeCheck(_h2py(h_obj), (PyTypeObject*)_h2py(h_type)); 35 | } 36 | 37 | _HPy_HIDDEN int 38 | ctx_Is(HPyContext *ctx, HPy h_obj, HPy h_other) 39 | { 40 | return _h2py(h_obj) == _h2py(h_other); 41 | } 42 | 43 | _HPy_HIDDEN HPy 44 | ctx_GetItem_i(HPyContext *ctx, HPy obj, HPy_ssize_t idx) { 45 | PyObject *py_obj = _h2py(obj); 46 | if (PySequence_Check(py_obj)) { 47 | return _py2h(PySequence_GetItem(py_obj, idx)); 48 | } 49 | PyObject* key = PyLong_FromSsize_t(idx); 50 | if (key == NULL) 51 | return HPy_NULL; 52 | HPy result = _py2h(PyObject_GetItem(py_obj, key)); 53 | Py_DECREF(key); 54 | return result; 55 | } 56 | 57 | _HPy_HIDDEN HPy 58 | ctx_GetItem_s(HPyContext *ctx, HPy obj, const char *key) { 59 | PyObject* key_o = PyUnicode_FromString(key); 60 | if (key_o == NULL) 61 | return HPy_NULL; 62 | HPy result = _py2h(PyObject_GetItem(_h2py(obj), key_o)); 63 | Py_DECREF(key_o); 64 | return result; 65 | } 66 | 67 | _HPy_HIDDEN int 68 | ctx_SetItem_i(HPyContext *ctx, HPy obj, HPy_ssize_t idx, HPy value) { 69 | PyObject* key = PyLong_FromSsize_t(idx); 70 | if (key == NULL) 71 | return -1; 72 | int result = PyObject_SetItem(_h2py(obj), key, _h2py(value)); 73 | Py_DECREF(key); 74 | return result; 75 | } 76 | 77 | _HPy_HIDDEN int 78 | ctx_SetItem_s(HPyContext *ctx, HPy obj, const char *key, HPy value) { 79 | PyObject* key_o = PyUnicode_FromString(key); 80 | if (key_o == NULL) 81 | return -1; 82 | int result = PyObject_SetItem(_h2py(obj), key_o, _h2py(value)); 83 | Py_DECREF(key_o); 84 | return result; 85 | } 86 | 87 | _HPy_HIDDEN int 88 | ctx_DelItem_i(HPyContext *ctx, HPy obj, HPy_ssize_t idx) { 89 | PyObject* key = PyLong_FromSsize_t(idx); 90 | if (key == NULL) 91 | return -1; 92 | int result = PyObject_DelItem(_h2py(obj), key); 93 | Py_DECREF(key); 94 | return result; 95 | } 96 | 97 | _HPy_HIDDEN int 98 | ctx_DelItem_s(HPyContext *ctx, HPy obj, const char *key) { 99 | PyObject* key_o = PyUnicode_FromString(key); 100 | if (key_o == NULL) 101 | return -1; 102 | int result = PyObject_DelItem(_h2py(obj), key_o); 103 | Py_DECREF(key_o); 104 | return result; 105 | } 106 | -------------------------------------------------------------------------------- /test/test_eval.py: -------------------------------------------------------------------------------- 1 | from .support import HPyTest 2 | 3 | class TestEval(HPyTest): 4 | def test_compile(self): 5 | import pytest 6 | from textwrap import dedent 7 | mod = self.make_module(""" 8 | HPyDef_METH(f, "f", HPyFunc_VARARGS) 9 | static HPy f_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 10 | { 11 | const char *source, *filename; 12 | HPy_SourceKind src_kind; 13 | int src_kind_i; 14 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ssi", &source, &filename, &src_kind_i)) 15 | return HPy_NULL; 16 | 17 | switch (src_kind_i) 18 | { 19 | case 0: src_kind = HPy_SourceKind_Expr; break; 20 | case 1: src_kind = HPy_SourceKind_File; break; 21 | case 2: src_kind = HPy_SourceKind_Single; break; 22 | default: 23 | // just pass through for testing 24 | src_kind = (HPy_SourceKind) src_kind_i; 25 | } 26 | return HPy_Compile_s(ctx, source, filename, src_kind); 27 | } 28 | @EXPORT(f) 29 | @INIT 30 | """) 31 | c0 = mod.f("1 + 2", "hello0.py", 0) 32 | assert c0 33 | assert c0.co_filename == "hello0.py" 34 | assert eval(c0) == 3 35 | 36 | c1 = mod.f(dedent(""" 37 | a = 1 38 | b = 2 39 | def add(x, y): 40 | return x + y 41 | res = add(a, b) 42 | """), "hello1.py", 1) 43 | globals1 = dict() 44 | locals1 = dict() 45 | assert eval(c1, globals1, locals1) is None 46 | assert "add" in locals1, "was: %r" % locals1 47 | assert locals1["a"] == 1 48 | assert locals1["b"] == 2 49 | assert locals1["res"] == 3 50 | 51 | c2 = mod.f("x = 1 + 2", "hello2.py", 2) 52 | locals2 = dict() 53 | assert eval(c2, dict(), locals2) is None 54 | assert locals2["x"] == 3 55 | 56 | with pytest.raises(SyntaxError): 57 | mod.f("1 +.", "hello1.c", 0) 58 | 59 | with pytest.raises(SystemError): 60 | mod.f("1+2", "hello.c", 777) 61 | 62 | def test_eval_code(self): 63 | import pytest 64 | mod = self.make_module(""" 65 | HPyDef_METH(f, "f", HPyFunc_VARARGS) 66 | static HPy f_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 67 | { 68 | if (nargs != 3) { 69 | HPyErr_SetString(ctx, ctx->h_TypeError, "expected exactly 3 args"); 70 | return HPy_NULL; 71 | } 72 | return HPy_EvalCode(ctx, args[0], args[1], args[2]); 73 | } 74 | @EXPORT(f) 75 | @INIT 76 | """) 77 | c0 = compile("a + b", "hello.py", "eval") 78 | assert mod.f(c0, dict(), dict(a=2, b=3)) == 5 79 | 80 | locals1 = dict(a=10, b=20) 81 | c1 = compile("x = a + b", "hello.py", "exec") 82 | assert mod.f(c1, dict(), locals1) is None 83 | assert locals1['x'] == 30 84 | 85 | c0 = compile("raise ValueError", "hello.py", "exec") 86 | with pytest.raises(ValueError): 87 | mod.f(c0, dict(__builtins__=__builtins__), dict()) 88 | -------------------------------------------------------------------------------- /hpy/debug/src/dhqueue.c: -------------------------------------------------------------------------------- 1 | #include "debug_internal.h" 2 | 3 | // TODO: we need to make DHQueue thread-safe if we want to use the same 4 | // context in multiple threads 5 | void DHQueue_init(DHQueue *q) { 6 | q->head = NULL; 7 | q->tail = NULL; 8 | q->size = 0; 9 | } 10 | 11 | void DHQueue_append(DHQueue *q, DHQueueNode *h) { 12 | if (q->head == NULL) { 13 | h->prev = NULL; 14 | h->next = NULL; 15 | q->head = h; 16 | q->tail = h; 17 | } else { 18 | h->next = NULL; 19 | h->prev = q->tail; 20 | q->tail->next = h; 21 | q->tail = h; 22 | } 23 | q->size++; 24 | } 25 | 26 | DHQueueNode *DHQueue_popfront(DHQueue *q) 27 | { 28 | assert(q->size > 0); 29 | assert(q->head != NULL); 30 | DHQueueNode *head = q->head; 31 | if (q->size == 1) { 32 | q->head = NULL; 33 | q->tail = NULL; 34 | q->size = 0; 35 | } 36 | else { 37 | q->head = head->next; 38 | q->head->prev = NULL; 39 | q->size--; 40 | } 41 | // the following is not strictly necessary, but it makes thing much easier 42 | // to debug in case of bugs 43 | head->next = NULL; 44 | head->prev = NULL; 45 | return head; 46 | } 47 | 48 | void DHQueue_remove(DHQueue *q, DHQueueNode *h) 49 | { 50 | #ifndef NDEBUG 51 | // if we are debugging, let's check that h is effectively in the queue 52 | DHQueueNode *it = q->head; 53 | bool found = false; 54 | while(it != NULL) { 55 | if (it == h) { 56 | found = true; 57 | break; 58 | } 59 | it = it->next; 60 | } 61 | assert(found); 62 | #endif 63 | if (q->size == 1) { 64 | q->head = NULL; 65 | q->tail = NULL; 66 | } else if (h == q->head) { 67 | assert(h->prev == NULL); 68 | q->head = h->next; 69 | q->head->prev = NULL; 70 | } else if (h == q->tail) { 71 | assert(h->next == NULL); 72 | q->tail = h->prev; 73 | q->tail->next = NULL; 74 | } 75 | else { 76 | h->prev->next = h->next; 77 | h->next->prev = h->prev; 78 | } 79 | q->size--; 80 | h->next = NULL; 81 | h->prev = NULL; 82 | } 83 | 84 | 85 | #ifndef NDEBUG 86 | static void linked_item_sanity_check(DHQueueNode *h) 87 | { 88 | if (h == NULL) 89 | return; 90 | if (h->next != NULL) 91 | assert(h->next->prev == h); 92 | if (h->prev != NULL) 93 | assert(h->prev->next == h); 94 | } 95 | #endif 96 | 97 | void DHQueue_sanity_check(DHQueue *q) 98 | { 99 | #ifndef NDEBUG 100 | if (q->head == NULL || q->tail == NULL) { 101 | assert(q->head == NULL); 102 | assert(q->tail == NULL); 103 | assert(q->size == 0); 104 | } 105 | else { 106 | assert(q->head->prev == NULL); 107 | assert(q->tail->next == NULL); 108 | assert(q->size > 0); 109 | DHQueueNode *h = q->head; 110 | HPy_ssize_t size = 0; 111 | while(h != NULL) { 112 | linked_item_sanity_check(h); 113 | if (h->next == NULL) 114 | assert(h == q->tail); 115 | h = h->next; 116 | size++; 117 | } 118 | assert(q->size == size); 119 | } 120 | #endif 121 | } 122 | -------------------------------------------------------------------------------- /proof-of-concept/pof.c: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | #include 3 | 4 | HPyDef_METH(do_nothing, "do_nothing", HPyFunc_NOARGS) 5 | static HPy do_nothing_impl(HPyContext *ctx, HPy self) 6 | { 7 | return HPy_Dup(ctx, ctx->h_None); 8 | } 9 | 10 | HPyDef_METH(double_obj, "double", HPyFunc_O) 11 | static HPy double_obj_impl(HPyContext *ctx, HPy self, HPy obj) 12 | { 13 | return HPy_Add(ctx, obj, obj); 14 | } 15 | 16 | HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS) 17 | static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 18 | { 19 | long a, b; 20 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b)) 21 | return HPy_NULL; 22 | return HPyLong_FromLong(ctx, a+b); 23 | } 24 | 25 | HPyDef_METH(add_ints_kw, "add_ints_kw", HPyFunc_KEYWORDS) 26 | static HPy add_ints_kw_impl(HPyContext *ctx, HPy self, const HPy *args, 27 | size_t nargs, HPy kwnames) 28 | { 29 | long a, b; 30 | const char* kwlist[] = {"a", "b", NULL}; 31 | if (!HPyArg_ParseKeywords(ctx, NULL, args, nargs, kwnames, "ll", 32 | kwlist, &a, &b)) 33 | return HPy_NULL; 34 | return HPyLong_FromLong(ctx, a+b); 35 | } 36 | 37 | typedef struct { 38 | double x; 39 | double y; 40 | } PointObject; 41 | 42 | HPyType_HELPERS(PointObject) 43 | 44 | HPyDef_SLOT(Point_new, HPy_tp_new) 45 | static HPy Point_new_impl (HPyContext *ctx, HPy cls, const HPy *args, 46 | HPy_ssize_t nargs, HPy kwnames) 47 | { 48 | double x, y; 49 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "dd", &x, &y)) 50 | return HPy_NULL; 51 | PointObject *point; 52 | HPy h_point = HPy_New(ctx, cls, &point); 53 | if (HPy_IsNull(h_point)) 54 | return HPy_NULL; 55 | point->x = x; 56 | point->y = y; 57 | return h_point; 58 | } 59 | 60 | HPyDef_SLOT(Point_repr, HPy_tp_repr) 61 | static HPy Point_repr_impl(HPyContext *ctx, HPy self) 62 | { 63 | PointObject *point = PointObject_AsStruct(ctx, self); 64 | char msg[256]; 65 | snprintf(msg, 256, "Point(%g, %g)", point->x, point->y); 66 | return HPyUnicode_FromString(ctx, msg); 67 | //return HPyUnicode_FromFormat("Point(%g, %g)", point->x, point->y); 68 | } 69 | 70 | 71 | static HPyDef *point_type_defines[] = { 72 | &Point_new, 73 | &Point_repr, 74 | NULL 75 | }; 76 | static HPyType_Spec point_type_spec = { 77 | .name = "pof.Point", 78 | .basicsize = sizeof(PointObject), 79 | .flags = HPy_TPFLAGS_DEFAULT, 80 | .defines = point_type_defines 81 | }; 82 | 83 | HPyDef_SLOT(mod_exec, HPy_mod_exec) 84 | static int mod_exec_impl(HPyContext *ctx, HPy m) 85 | { 86 | HPy h_point_type = HPyType_FromSpec(ctx, &point_type_spec, NULL); 87 | if (HPy_IsNull(h_point_type)) 88 | return -1; 89 | HPy_SetAttr_s(ctx, m, "Point", h_point_type); 90 | HPy_Close(ctx, h_point_type); 91 | return 0; 92 | } 93 | 94 | static HPyDef *module_defines[] = { 95 | &do_nothing, 96 | &double_obj, 97 | &add_ints, 98 | &add_ints_kw, 99 | &mod_exec, 100 | NULL 101 | }; 102 | 103 | static HPyModuleDef moduledef = { 104 | .doc = "HPy Proof of Concept", 105 | .size = 0, 106 | .defines = module_defines 107 | }; 108 | 109 | HPy_MODINIT(pof, moduledef) 110 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: hpy.universal 3 | 4 | .PHONY: hpy.universal 5 | hpy.universal: 6 | python3 setup.py build_clib -f build_ext -if 7 | 8 | .PHONY: dist-info 9 | dist-info: 10 | python3 setup.py dist_info 11 | 12 | debug: 13 | HPY_DEBUG_BUILD=1 make all 14 | 15 | autogen: 16 | python3 -m hpy.tools.autogen . 17 | 18 | cppcheck-build-dir: 19 | mkdir -p $(or ${CPPCHECK_BUILD_DIR}, .cppcheck) 20 | 21 | .PHONY: cppcheck 22 | cppcheck: cppcheck-build-dir 23 | # azure pipelines doesn't show stderr, so we write the errors to a file and cat it later :( 24 | $(eval PYTHON_INC = $(shell python3 -q -c "from sysconfig import get_paths as gp; print(gp()['include'])")) 25 | $(eval PYTHON_PLATINC = $(shell python3 -q -c "from sysconfig import get_paths as gp; print(gp()['platinclude'])")) 26 | cppcheck --version 27 | cppcheck \ 28 | -v \ 29 | --error-exitcode=1 \ 30 | --cppcheck-build-dir=$(or ${CPPCHECK_BUILD_DIR}, .cppcheck) \ 31 | --enable=warning,performance,portability,information,missingInclude \ 32 | --inline-suppr \ 33 | --suppress=syntaxError \ 34 | -I /usr/local/include/ \ 35 | -I /usr/include/ \ 36 | -I ${PYTHON_INC} \ 37 | -I ${PYTHON_PLATINC} \ 38 | -I . \ 39 | -I hpy/devel/include/ \ 40 | -I hpy/devel/include/hpy/ \ 41 | -I hpy/devel/include/hpy/cpython/ \ 42 | -I hpy/devel/include/hpy/universal/ \ 43 | -I hpy/devel/include/hpy/runtime/ \ 44 | -I hpy/universal/src/ \ 45 | -I hpy/debug/src/ \ 46 | -I hpy/debug/src/include \ 47 | -I hpy/trace/src/ \ 48 | -I hpy/trace/src/include \ 49 | --force \ 50 | -D NULL=0 \ 51 | -D HPY_ABI_CPYTHON \ 52 | -D __linux__=1 \ 53 | -D __x86_64__=1 \ 54 | -D __LP64__=1 \ 55 | . 56 | 57 | infer: 58 | python3 setup.py build_ext -if -U NDEBUG | compiledb 59 | # see commit cd8cd6e for why we need to ignore debug_ctx.c 60 | @infer --fail-on-issue --compilation-database compile_commands.json --report-blacklist-path-regex "hpy/debug/src/debug_ctx.c" 61 | 62 | valgrind_args = --suppressions=hpy/tools/valgrind/python.supp --suppressions=hpy/tools/valgrind/hpy.supp --leak-check=full --show-leak-kinds=definite,indirect --log-file=/tmp/valgrind-output 63 | python_args = -m pytest --valgrind --valgrind-log=/tmp/valgrind-output 64 | 65 | .PHONY: valgrind 66 | valgrind: 67 | ifeq ($(HPY_TEST_PORTION),) 68 | PYTHONMALLOC=malloc valgrind $(valgrind_args) python3 $(python_args) test/ 69 | else 70 | PYTHONMALLOC=malloc valgrind $(valgrind_args) python3 $(python_args) --portion $(HPY_TEST_PORTION) test/ 71 | endif 72 | 73 | porting-example-tests: 74 | cd docs/porting-example/steps && python3 setup00.py build_ext -i 75 | cd docs/porting-example/steps && python3 setup01.py build_ext -i 76 | cd docs/porting-example/steps && python3 setup02.py build_ext -i 77 | cd docs/porting-example/steps && python3 setup03.py --hpy-abi=universal build_ext -i 78 | python3 -m pytest docs/porting-example/steps/ ${TEST_ARGS} 79 | 80 | docs-examples-tests: 81 | cd docs/examples/simple-example && python3 setup.py --hpy-abi=universal install 82 | cd docs/examples/mixed-example && python3 setup.py install 83 | cd docs/examples/snippets && python3 setup.py --hpy-abi=universal install 84 | cd docs/examples/quickstart && python3 setup.py --hpy-abi=universal install 85 | cd docs/examples/hpytype-example && python3 setup.py --hpy-abi=universal install 86 | python3 -m pytest docs/examples/tests.py ${TEST_ARGS} 87 | -------------------------------------------------------------------------------- /proof-of-concept/pofcpp.cpp: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | #include 3 | 4 | HPyDef_METH(do_nothing, "do_nothing", HPyFunc_NOARGS) 5 | static HPy do_nothing_impl(HPyContext *ctx, HPy self) 6 | { 7 | return HPy_Dup(ctx, ctx->h_None); 8 | } 9 | 10 | HPyDef_METH(double_obj, "double", HPyFunc_O) 11 | static HPy double_obj_impl(HPyContext *ctx, HPy self, HPy obj) 12 | { 13 | return HPy_Add(ctx, obj, obj); 14 | } 15 | 16 | HPyDef_METH(add_ints, "add_ints", HPyFunc_VARARGS) 17 | static HPy add_ints_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 18 | { 19 | long a, b; 20 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "ll", &a, &b)) 21 | return HPy_NULL; 22 | return HPyLong_FromLong(ctx, a+b); 23 | } 24 | 25 | HPyDef_METH(add_ints_kw, "add_ints_kw", HPyFunc_KEYWORDS) 26 | static HPy add_ints_kw_impl(HPyContext *ctx, HPy self, const HPy *args, 27 | size_t nargs, HPy kwnames) 28 | { 29 | long a, b; 30 | const char* kwlist[] = {"a", "b", NULL}; 31 | if (!HPyArg_ParseKeywords(ctx, NULL, args, nargs, kwnames, "ll", 32 | kwlist, &a, &b)) 33 | return HPy_NULL; 34 | return HPyLong_FromLong(ctx, a+b); 35 | } 36 | 37 | typedef struct { 38 | double x; 39 | double y; 40 | } PointObject; 41 | 42 | HPyType_HELPERS(PointObject) 43 | 44 | HPyDef_SLOT(Point_new, HPy_tp_new) 45 | static HPy Point_new_impl (HPyContext *ctx, HPy cls, const HPy *args, 46 | HPy_ssize_t nargs, HPy kwnames) 47 | { 48 | double x, y; 49 | if (!HPyArg_Parse(ctx, NULL, args, nargs, "dd", &x, &y)) 50 | return HPy_NULL; 51 | PointObject *point; 52 | HPy h_point = HPy_New(ctx, cls, &point); 53 | if (HPy_IsNull(h_point)) 54 | return HPy_NULL; 55 | point->x = x; 56 | point->y = y; 57 | return h_point; 58 | } 59 | 60 | HPyDef_SLOT(Point_repr, HPy_tp_repr) 61 | static HPy Point_repr_impl(HPyContext *ctx, HPy self) 62 | { 63 | PointObject *point = PointObject_AsStruct(ctx, self); 64 | char msg[256]; 65 | snprintf(msg, 256, "Point(%g, %g)", point->x, point->y); 66 | return HPyUnicode_FromString(ctx, msg); 67 | //return HPyUnicode_FromFormat("Point(%g, %g)", point->x, point->y); 68 | } 69 | 70 | 71 | static HPyDef *point_type_defines[] = { 72 | &Point_new, 73 | &Point_repr, 74 | NULL 75 | }; 76 | 77 | static HPyType_Spec point_type_spec = { 78 | .name = "pofcpp.Point", 79 | .basicsize = sizeof(PointObject), 80 | .flags = HPy_TPFLAGS_DEFAULT, 81 | .defines = point_type_defines 82 | }; 83 | 84 | HPyDef_SLOT(mod_exec, HPy_mod_exec) 85 | static int mod_exec_impl(HPyContext *ctx, HPy m) 86 | { 87 | HPy h_point_type = HPyType_FromSpec(ctx, &point_type_spec, NULL); 88 | if (HPy_IsNull(h_point_type)) 89 | return -1; 90 | HPy_SetAttr_s(ctx, m, "Point", h_point_type); 91 | HPy_Close(ctx, h_point_type); 92 | return 0; 93 | } 94 | 95 | static HPyDef *module_defines[] = { 96 | &do_nothing, 97 | &double_obj, 98 | &add_ints, 99 | &add_ints_kw, 100 | &mod_exec, 101 | NULL 102 | }; 103 | static HPyModuleDef moduledef = { 104 | .doc = "HPy c++ Proof of Concept", 105 | .size = 0, 106 | .defines = module_defines 107 | }; 108 | 109 | #ifdef __cplusplus 110 | extern "C" { 111 | #endif 112 | 113 | HPy_MODINIT(pofcpp, moduledef) 114 | 115 | #ifdef __cplusplus 116 | } 117 | #endif 118 | -------------------------------------------------------------------------------- /microbench/README.md: -------------------------------------------------------------------------------- 1 | # To run the microbenchmarks 2 | 3 | ## Non-Python dependencies 4 | 5 | The benchmarks depends on Valgrind, which can be installed with 6 | 7 | ```sh 8 | sudo apt update && sudo apt install -y valgrind 9 | ``` 10 | 11 | Alternatively, you can also use [Pixi] to get it in a new shell with 12 | 13 | ```sh 14 | pixi shell 15 | ``` 16 | 17 | Similarly, building with GraalPy requires `libffi` to build the Python package `ffi`. 18 | `pixi shell` provides it. 19 | 20 | [UV] can be useful since it is used in few Makefile targets to install Python interpreters. 21 | 22 | ## Python virtual environments 23 | 24 | We assume in the following that a virtual environment is activated. One can create 25 | environments with the Makefile targets `create_venv_...` as 26 | 27 | ```sh 28 | cd /path/to/hpy/microbench 29 | make create_venv_pypy 30 | . .venv_pypy/bin/activate 31 | ``` 32 | 33 | ## Non-editable install with build isolation 34 | 35 | One can build these microbenchmarks with HPy from PyPI (on CPython) or bundled with the Python implementation. 36 | 37 | ```sh 38 | pip install . 39 | ``` 40 | 41 | This builds the HPy extension with the CPython ABI for CPython and with the universal ABI for other implementations. 42 | To build this extension with the universal ABI with CPython: 43 | 44 | ```sh 45 | pip install . --config-settings="--global-option=--hpy-abi=universal" 46 | ``` 47 | 48 | ## Editable install without build isolation 49 | 50 | 1. On CPython, you need to have `hpy` installed in your virtualenv. The easiest way 51 | to do it is: 52 | 53 | ```sh 54 | cd /path/to/hpy 55 | pip install -e . 56 | ``` 57 | 58 | 2. Install build and runtime dependencies 59 | 60 | ```sh 61 | # cffi needed to build _valgrind 62 | pip install cffi pytest 63 | ``` 64 | 65 | 3. Build and install the extension modules needed for the microbenchmarks 66 | 67 | ```sh 68 | cd /path/to/hpy/microbench 69 | pip install . --no-build-isolation 70 | # or for the universal ABI (on with CPython) 71 | rm -f src/*.so src/hpy_simple.py 72 | pip install -e . --no-build-isolation --config-settings="--global-option=--hpy-abi=universal" 73 | ``` 74 | 75 | ## Run the benchmarks 76 | 77 | ```sh 78 | pytest -v 79 | ``` 80 | 81 | To run only cpy or hpy tests, use -m (to select markers): 82 | 83 | ```sh 84 | pytest -v -m hpy 85 | pytest -v -m cpy 86 | ``` 87 | 88 | ## Comparing alternative Python implementations to CPython 89 | 90 | One can run things like 91 | 92 | ```sh 93 | make cleanall 94 | pixi shell 95 | make create_venv_cpy 96 | make create_venv_pypy 97 | make create_venv_graalpy 98 | 99 | make install PYTHON=.venv_cpy/bin/python 100 | make install PYTHON=.venv_pypy/bin/python 101 | make install PYTHON=.venv_graalpy/bin/python 102 | 103 | make bench PYTHON=.venv_cpy/bin/python 104 | make bench PYTHON=.venv_pypy/bin/python 105 | # only HPy for GraalPy since the full benchmarks are a bit too long 106 | make bench_hpy PYTHON=.venv_graalpy/bin/python 107 | 108 | make print_cpy 109 | make print_pypy 110 | make print_graalpy 111 | 112 | make print_pypy_vs_cpy 113 | make print_graalpy_vs_cpy 114 | 115 | # example comparison on only one test 116 | .venv_cpy/bin/python -m pytest test_microbench.py::TestType::test_allocate_obj[cpy] 117 | .venv_pypy/bin/python -m pytest test_microbench.py::TestType::test_allocate_obj[hpy] 118 | ``` 119 | 120 | [Pixi]: https://pixi.sh 121 | [UV]: https://docs.astral.sh/uv/ 122 | -------------------------------------------------------------------------------- /docs/xxx-unsorted-notes.txt: -------------------------------------------------------------------------------- 1 | Goal: better C API for CPython and 1st-class citizen on PyPy 2 | 3 | - everything should be opaque by default 4 | 5 | - should we have an API to "query" if an object supports a certain low-level layout? E.g list-of-integer 6 | 7 | - should we have an API to e.g. ask "give me the nth C-level long in the 8 | list? And then rely on the compiler to turn this into an efficient loop: 9 | long PyObject_GetLongItem(PyHandle h, long index) ? 10 | 11 | 12 | - we need PyObject_GetItem(handle) and PyObject_GetItem(int_index) 13 | 14 | - PyHandle PyObject_GetMappingProtocol(PyHandle o): ask a python object if it 15 | has the "mapping" interface; then you can close it when you are done with it 16 | and e.g. PyPy can release when it's no longer needed 17 | 18 | - should we write a tool to convert from the old API to the new API? 19 | 20 | - how we do deploy it? Should we have a single PyHandle.h file which is enough 21 | to include? Or do like numpy.get_include_dirs()? 22 | 23 | - we need to do what cffi does (and ship our version on PyPy) 24 | 25 | - cython might need/want to ship its own vendored version of PyHandle 26 | 27 | - we need a versioning system which is possible to query at runtime? (to check 28 | that it was compiled with the "correct/expected" PyHandle version 29 | 30 | - what to do with existing code which actively check whether the refcount is 1? E.g. PyString_Resize? 31 | 32 | - fast c-to-c calls: should we use argument clinic or something similar? 33 | 34 | 35 | 36 | Protocol sketch 37 | ---------------- 38 | 39 | HPySequence_long x = HPy_AsSequence_long(obj); /* it is possible to fail and you should be ready to handle the fallback */ 40 | int len = HPy_Sequence_Len_long(x, obj); 41 | for(int i=0; i sequence index access 82 | * comparisons (<, <=, ==, !=, >=, >) 83 | * rich comparisons 84 | * boolean comparisons (0/1) 85 | * call (call/vectorcall/method/special-method) 86 | * e.g. PyCall_SpecialMethodOneArg(obj, __add__, arg) -> call specific macro 87 | * … 88 | * context manager (enter/exit) 89 | * -> call special method 90 | * async (await/aiter/anext) 91 | * -> call special method 92 | -------------------------------------------------------------------------------- /microbench/src/hpy_simple.c: -------------------------------------------------------------------------------- 1 | #include "hpy.h" 2 | 3 | /* module-level functions */ 4 | 5 | HPyDef_METH(noargs, "noargs", HPyFunc_NOARGS) 6 | static HPy noargs_impl(HPyContext *ctx, HPy self) 7 | { 8 | return HPy_Dup(ctx, ctx->h_None); 9 | } 10 | 11 | HPyDef_METH(onearg, "onearg", HPyFunc_O) 12 | static HPy onearg_impl(HPyContext *ctx, HPy self, HPy arg) 13 | { 14 | return HPy_Dup(ctx, ctx->h_None); 15 | } 16 | 17 | HPyDef_METH(varargs, "varargs", HPyFunc_VARARGS) 18 | static HPy varargs_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 19 | { 20 | return HPy_Dup(ctx, ctx->h_None); 21 | } 22 | 23 | HPyDef_METH(call_with_tuple, "call_with_tuple", HPyFunc_VARARGS) 24 | static HPy call_with_tuple_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 25 | { 26 | HPy f, f_args; 27 | if (nargs != 2) { 28 | HPyErr_SetString(ctx, ctx->h_TypeError, "call_with_tuple requires two arguments"); 29 | return HPy_NULL; 30 | } 31 | f = args[0]; 32 | f_args = args[1]; 33 | return HPy_CallTupleDict(ctx, f, f_args, HPy_NULL); 34 | } 35 | 36 | HPyDef_METH(call_with_tuple_and_dict, "call_with_tuple_and_dict", HPyFunc_VARARGS) 37 | static HPy call_with_tuple_and_dict_impl(HPyContext *ctx, HPy self, const HPy *args, size_t nargs) 38 | { 39 | HPy f, f_args, f_kw; 40 | if (nargs != 3) { 41 | HPyErr_SetString(ctx, ctx->h_TypeError, "call_with_tuple_and_dict requires three arguments"); 42 | return HPy_NULL; 43 | } 44 | f = args[0]; 45 | f_args = args[1]; 46 | f_kw = args[2]; 47 | return HPy_CallTupleDict(ctx, f, f_args, f_kw); 48 | } 49 | 50 | HPyDef_METH(allocate_int, "allocate_int", HPyFunc_NOARGS) 51 | static HPy allocate_int_impl(HPyContext *ctx, HPy self) 52 | { 53 | return HPyLong_FromLong(ctx, 2048); 54 | } 55 | 56 | HPyDef_METH(allocate_tuple, "allocate_tuple", HPyFunc_NOARGS) 57 | static HPy allocate_tuple_impl(HPyContext *ctx, HPy self) 58 | { 59 | return HPy_BuildValue(ctx, "ii", 2048, 2049); 60 | } 61 | 62 | 63 | /* Foo type */ 64 | 65 | typedef struct { 66 | long x; 67 | long y; 68 | } FooObject; 69 | 70 | HPyDef_SLOT(Foo_getitem, HPy_sq_item) 71 | static HPy Foo_getitem_impl(HPyContext *ctx, HPy self, HPy_ssize_t i) 72 | { 73 | return HPy_Dup(ctx, ctx->h_None); 74 | } 75 | 76 | HPyDef_SLOT(Foo_len, HPy_sq_length) 77 | static HPy_ssize_t Foo_len_impl(HPyContext *ctx, HPy self) 78 | { 79 | return 42; 80 | } 81 | 82 | 83 | // note that we can reuse the same HPyDef for both module-level and type-level 84 | // methods 85 | static HPyDef *foo_defines[] = { 86 | &noargs, 87 | &onearg, 88 | &varargs, 89 | &allocate_int, 90 | &allocate_tuple, 91 | &Foo_getitem, 92 | &Foo_len, 93 | }; 94 | 95 | 96 | static HPyType_Spec Foo_spec = { 97 | .name = "hpy_simple.Foo", 98 | .basicsize = sizeof(FooObject), 99 | .flags = HPy_TPFLAGS_DEFAULT, 100 | .defines = foo_defines 101 | }; 102 | 103 | 104 | /* Module defines */ 105 | 106 | HPyDef_SLOT(init_hpy_simple, HPy_mod_exec) 107 | static int init_hpy_simple_impl(HPyContext *ctx, HPy m) 108 | { 109 | HPy h_Foo = HPyType_FromSpec(ctx, &Foo_spec, NULL); 110 | if (HPy_IsNull(h_Foo)) 111 | return -1; 112 | HPy_SetAttr_s(ctx, m, "Foo", h_Foo); 113 | HPy_SetAttr_s(ctx, m, "HTFoo", h_Foo); 114 | return 0; 115 | } 116 | 117 | static HPyDef *module_defines[] = { 118 | &noargs, 119 | &onearg, 120 | &varargs, 121 | &call_with_tuple, 122 | &call_with_tuple_and_dict, 123 | &allocate_int, 124 | &allocate_tuple, 125 | &init_hpy_simple, 126 | NULL 127 | }; 128 | 129 | static HPyModuleDef moduledef = { 130 | .doc = "HPy microbenchmarks", 131 | .size = 0, 132 | .defines = module_defines, 133 | }; 134 | 135 | HPy_MODINIT(hpy_simple, moduledef) 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HPy: a better API for Python 2 | 3 | [![Build](https://github.com/hpyproject/hpy/actions/workflows/ci.yml/badge.svg)](https://github.com/hpyproject/hpy/actions/workflows/ci.yml) 4 | [![Documentation](https://readthedocs.org/projects/hpy/badge/)](https://hpy.readthedocs.io/) 5 | [![Join the discord server at https://discord.gg/xSzxUbPkTQ](https://img.shields.io/discord/1077164940906995813.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square)](https://discord.gg/xSzxUbPkTQ) 6 | 7 | **Website**: [hpyproject.org](https://hpyproject.org/) \ 8 | **Community**: [HPy Discord server](https://discord.gg/xSzxUbPkTQ) \ 9 | **Mailing list**: [hpy-dev@python.org](https://mail.python.org/mailman3/lists/hpy-dev.python.org/) 10 | 11 | ## Summary 12 | 13 | HPy is a better API for extending Python 14 | in C. The old C API is specific to the current implementation of CPython. 15 | It exposes a lot of internal details which makes it hard to: 16 | 17 | - implement it for other Python implementations (e.g. PyPy, GraalPy, 18 | Jython, IronPython, etc.). 19 | - experiment with new things inside CPython itself: e.g. using a GC 20 | instead of refcounting, or to remove the GIL 21 | - guarantee binary stability 22 | 23 | HPy is a specification of a new API and ABI for extending Python that is 24 | Python implementation agnostic and designed to hide and abstract internal 25 | details such that it: 26 | 27 | - can stay binary compatible even if the underlying Python internals change significantly 28 | - does not hinder internal progress of CPython and other Pythons 29 | 30 | Please read the [documentation](https://docs.hpyproject.org/en/latest/overview.html) 31 | for more details on HPy motivation, goals, and features, for example: 32 | 33 | - debug mode for better developer experience 34 | - support for incremental porting from CPython API to HPy 35 | - CPython ABI for raw performance on CPython 36 | - and others 37 | 38 | Do you want to see how HPy API looks in code? Check out 39 | our [quickstart example](https://docs.hpyproject.org/en/latest/quickstart.html). 40 | 41 | You may also be interested in HPy's 42 | [API reference](https://docs.hpyproject.org/en/latest/api-reference/index.html). 43 | 44 | This repository contains the API and ABI specification and implementation 45 | for the CPython interpreter. Other interpreters that support HPy natively: GraalPy 46 | and PyPy, provide their own builtin HPy implementations. 47 | 48 | 49 | ## Why should I care about this stuff? 50 | 51 | - the existing C API is becoming a problem for CPython and for the 52 | evolution of the language itself: this project makes it possible to make 53 | experiments which might be "officially" adopted in the future 54 | 55 | - for PyPy, it will give obvious speed benefits: for example, data 56 | scientists will be able to get the benefit of fast C libraries *and* fast 57 | Python code at the same time, something which is hard to achieve now 58 | 59 | - the current implementation is too tied to CPython and proved to be a 60 | problem for almost all the other alternative implementations. Having an 61 | API which is designed to work well on two different implementations will 62 | make the job much easier for future ones: going from 2 to N is much easier 63 | than going from 1 to 2 64 | 65 | - arguably, it will be easier to learn and understand than the massive 66 | CPython C API 67 | 68 | See also [Python Performance: Past, Present, 69 | Future](https://github.com/vstinner/talks/raw/main/2019-EuroPython/python_performance.pdf) 70 | by Victor Stinner. 71 | 72 | 73 | ## What does `HPy` mean? 74 | 75 | The "H" in `HPy` stands for "handle": one of the key idea of the new API is to 76 | use fully opaque handles to represent and pass around Python objects. 77 | 78 | 79 | ## Donate to HPy 80 | 81 | Become a financial contributor and help us sustain the HPy community: [Contribute to HPy](https://opencollective.com/hpy/contribute). 82 | -------------------------------------------------------------------------------- /hpy/tools/autogen/ctx.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from pycparser import c_ast 3 | from .autogenfile import AutoGenFile 4 | from .parse import toC, find_typedecl, maybe_make_void 5 | 6 | 7 | class autogen_ctx_h(AutoGenFile): 8 | PATH = 'hpy/devel/include/hpy/universal/autogen_ctx.h' 9 | 10 | ## struct _HPyContext_s { 11 | ## const char *name; 12 | ## void *_private; 13 | ## int abi_version; 14 | ## HPy h_None; 15 | ## ... 16 | ## HPy (*ctx_Module_Create)(HPyContext *ctx, HPyModuleDef *def); 17 | ## ... 18 | ## } 19 | 20 | def generate(self): 21 | # Put all declarations (variables and functions) into one list in order 22 | # to be able to sort them by their given context index. 23 | # We need to remember the output function per item (either 24 | # 'declare_var' or 'declare_func'). So, we create tuples with structure 25 | # '(decl, declare_*)'. 26 | all_decls = [(x, self.declare_var) for x in self.api.variables] 27 | all_decls += [(x, self.declare_func) for x in self.api.functions] 28 | 29 | # sort the list of all declaration by 'decl.ctx_index' 30 | all_decls.sort(key=lambda x: x[0].ctx_index) 31 | 32 | lines = [] 33 | w = lines.append 34 | w('struct _HPyContext_s {') 35 | w(' const char *name; // used just to make debugging and testing easier') 36 | w(' void *_private; // used by implementations to store custom data') 37 | w(' int abi_version;') 38 | for var, cons in all_decls: 39 | w(' %s;' % cons(var)) 40 | w('};') 41 | return '\n'.join(lines) 42 | 43 | def declare_var(self, var): 44 | return toC(var.node) 45 | 46 | def declare_func(self, func): 47 | # e.g. "HPy (*ctx_Module_Create)(HPyContext *ctx, HPyModuleDef *def)" 48 | # 49 | # turn the function declaration into a function POINTER declaration 50 | newnode = deepcopy(func.node) 51 | newnode.type = c_ast.PtrDecl(type=newnode.type, quals=[]) 52 | maybe_make_void(func, newnode) 53 | 54 | # fix the name of the function pointer 55 | typedecl = find_typedecl(newnode) 56 | typedecl.declname = func.ctx_name() 57 | return toC(newnode) 58 | 59 | 60 | class autogen_ctx_def_h(AutoGenFile): 61 | PATH = 'hpy/universal/src/autogen_ctx_def.h' 62 | 63 | ## struct _HPyContext_s g_universal_ctx = { 64 | ## .name = "...", 65 | ## ._private = NULL, 66 | ## .abi_version = HPY_ABI_VERSION, 67 | ## .h_None = {CONSTANT_H_NONE}, 68 | ## ... 69 | ## .ctx_Module_Create = &ctx_Module_Create, 70 | ## ... 71 | ## } 72 | 73 | def generate(self): 74 | lines = [] 75 | w = lines.append 76 | w('struct _HPyContext_s g_universal_ctx = {') 77 | w(' .name = "HPy Universal ABI (CPython backend)",') 78 | w(' ._private = NULL,') 79 | w(' .abi_version = HPY_ABI_VERSION,') 80 | w(' /* h_None & co. are initialized by init_universal_ctx() */') 81 | for func in self.api.functions: 82 | w(' .%s = &%s,' % (func.ctx_name(), func.ctx_name())) 83 | w('};') 84 | return '\n'.join(lines) 85 | 86 | 87 | class cpython_autogen_ctx_h(AutoGenFile): 88 | PATH = 'hpy/devel/include/hpy/cpython/autogen_ctx.h' 89 | 90 | def generate(self): 91 | # Put all variable declarations into a list in order 92 | # to be able to sort them by their given context index. 93 | var_decls = list(self.api.variables) 94 | 95 | # sort the list of var declaration by 'decl.ctx_index' 96 | var_decls.sort(key=lambda x: x.ctx_index) 97 | 98 | lines = [] 99 | w = lines.append 100 | w('struct _HPyContext_s {') 101 | w(' const char *name;') 102 | w(' int abi_version;') 103 | for var in var_decls: 104 | w(' %s;' % toC(var.node)) 105 | w('};') 106 | return '\n'.join(lines) 107 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_long.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | 4 | #ifndef HPY_ABI_CPYTHON 5 | // for _h2py and _py2h 6 | # include "handles.h" 7 | #endif 8 | 9 | // just for better readability 10 | #define SIZEOF_INT32 4 11 | #define SIZEOF_INT64 8 12 | 13 | HPyAPI_IMPL HPy ctx_Long_FromInt32_t(HPyContext *ctx, int32_t value) { 14 | #if SIZEOF_LONG >= SIZEOF_INT32 15 | return _py2h(PyLong_FromLong((long)value)); 16 | #else 17 | #error "unsupported SIZEOF_LONG" 18 | #endif 19 | } 20 | 21 | HPyAPI_IMPL HPy ctx_Long_FromUInt32_t(HPyContext *ctx, uint32_t value) { 22 | #if SIZEOF_LONG >= SIZEOF_INT32 23 | return _py2h(PyLong_FromUnsignedLong((unsigned long)value)); 24 | #else 25 | #error "unsupported SIZEOF_LONG" 26 | #endif 27 | } 28 | 29 | HPyAPI_IMPL HPy ctx_Long_FromInt64_t(HPyContext *ctx, int64_t v) { 30 | #if SIZEOF_LONG >= SIZEOF_INT64 31 | return _py2h(PyLong_FromLong((long)v)); 32 | #elif SIZEOF_LONG_LONG >= SIZEOF_INT64 33 | return _py2h(PyLong_FromLongLong((long long)v)); 34 | #else 35 | #error "unsupported SIZEOF_LONG_LONG" 36 | #endif 37 | } 38 | 39 | HPyAPI_IMPL HPy ctx_Long_FromUInt64_t(HPyContext *ctx, uint64_t v) { 40 | #if SIZEOF_LONG >= SIZEOF_INT64 41 | return _py2h(PyLong_FromUnsignedLong((unsigned long)v)); 42 | #elif SIZEOF_LONG_LONG >= SIZEOF_INT64 43 | return _py2h(PyLong_FromUnsignedLongLong((unsigned long long)v)); 44 | #else 45 | #error "unsupported SIZEOF_LONG_LONG" 46 | #endif 47 | } 48 | 49 | HPyAPI_IMPL int32_t ctx_Long_AsInt32_t(HPyContext *ctx, HPy h) { 50 | long lres = PyLong_AsLong(_h2py(h)); 51 | #if SIZEOF_LONG == SIZEOF_INT32 52 | return (int32_t) lres; 53 | #elif SIZEOF_LONG >= SIZEOF_INT32 54 | int32_t i32res = (int32_t) lres; 55 | if (lres == (long) i32res) 56 | return i32res; 57 | PyErr_SetString(PyExc_OverflowError, 58 | "Python int too large to convert to C int32_t"); 59 | return (int32_t) -1; 60 | #else 61 | #error "unsupported SIZEOF_LONG" 62 | #endif 63 | } 64 | 65 | HPyAPI_IMPL uint32_t ctx_Long_AsUInt32_t(HPyContext *ctx, HPy h) { 66 | unsigned long lres = PyLong_AsUnsignedLong(_h2py(h)); 67 | #if SIZEOF_LONG == SIZEOF_INT32 68 | return (uint32_t) lres; 69 | #elif SIZEOF_LONG >= SIZEOF_INT32 70 | uint32_t ui32res = (uint32_t) lres; 71 | if (lres == (unsigned long) ui32res) 72 | return ui32res; 73 | PyErr_SetString(PyExc_OverflowError, 74 | "Python int too large to convert to C uint32_t"); 75 | return (uint32_t) -1; 76 | #else 77 | #error "unsupported SIZEOF_LONG" 78 | #endif 79 | } 80 | 81 | HPyAPI_IMPL uint32_t ctx_Long_AsUInt32_tMask(HPyContext *ctx, HPy h) { 82 | return (uint32_t) PyLong_AsUnsignedLongMask(_h2py(h)); 83 | } 84 | 85 | HPyAPI_IMPL int64_t ctx_Long_AsInt64_t(HPyContext *ctx, HPy h) { 86 | long long lres = PyLong_AsLongLong(_h2py(h)); 87 | #if SIZEOF_LONG_LONG == SIZEOF_INT64 88 | return (int64_t) lres; 89 | #elif SIZEOF_LONG_LONG >= SIZEOF_INT64 90 | int64_t i64res = (int64_t) lres; 91 | if (lres == (long long) i64res) 92 | return i64res; 93 | PyErr_SetString(PyExc_OverflowError, 94 | "Python int too large to convert to C int64_t"); 95 | return (int64_t) -1; 96 | #else 97 | #error "unsupported SIZEOF_LONG_LONG" 98 | #endif 99 | } 100 | 101 | HPyAPI_IMPL uint64_t ctx_Long_AsUInt64_t(HPyContext *ctx, HPy h) { 102 | unsigned long long lres = PyLong_AsUnsignedLongLong(_h2py(h)); 103 | #if SIZEOF_LONG_LONG == SIZEOF_INT64 104 | return (uint64_t) lres; 105 | #elif SIZEOF_LONG_LONG >= SIZEOF_INT64 106 | uint64_t ui64res = (uint64_t) lres; 107 | if (lres == (unsigned long long) ui64res) 108 | return ui64res; 109 | PyErr_SetString(PyExc_OverflowError, 110 | "Python int too large to convert to C uint64_t"); 111 | return (uint64_t) -1; 112 | #else 113 | #error "unsupported SIZEOF_LONG_LONG" 114 | #endif 115 | } 116 | 117 | HPyAPI_IMPL uint64_t ctx_Long_AsUInt64_tMask(HPyContext *ctx, HPy h) { 118 | return (uint64_t) PyLong_AsUnsignedLongLongMask(_h2py(h)); 119 | } 120 | -------------------------------------------------------------------------------- /hpy/devel/include/hpy/runtime/structseq.h: -------------------------------------------------------------------------------- 1 | #ifndef HPY_COMMON_RUNTIME_STRUCTSEQ_H 2 | #define HPY_COMMON_RUNTIME_STRUCTSEQ_H 3 | 4 | #include "hpy.h" 5 | 6 | /* 7 | * Struct sequences are subclasses of tuple, so we provide a simplified API to 8 | * create them here. This maps closely to the CPython limited API for creating 9 | * struct sequences. However, in universal mode we use the 10 | * collections.namedtuple type to implement this, which behaves a bit 11 | * differently w.r.t. hidden elements. Thus, the n_in_sequence field available 12 | * in CPython's PyStructSequence_Desc is not available. Also, we use a builder 13 | * API like for tuples and lists so that the struct sequence is guaranteed not 14 | * to be written after it is created. 15 | */ 16 | 17 | /** 18 | * Describes a field of a struct sequence. 19 | */ 20 | typedef struct { 21 | /** 22 | * Name (UTF-8 encoded) for the field or ``NULL`` to end the list of named 23 | * fields. Set the name to :c:var:`HPyStructSequence_UnnamedField` to leave 24 | * it unnamed. 25 | */ 26 | const char *name; 27 | 28 | /** 29 | * Docstring of the field (UTF-8 encoded); may be ``NULL``. 30 | */ 31 | const char *doc; 32 | } HPyStructSequence_Field; 33 | 34 | /** 35 | * Contains the meta information of a struct sequence type to create. 36 | * Struct sequences are subclasses of tuple. The index in the :c:member:`fields` 37 | * array of the descriptor determines which field of the struct sequence is 38 | * described. 39 | */ 40 | typedef struct { 41 | /** 42 | * Name of the struct sequence type (UTF-8 encoded; must not be ``NULL``). 43 | */ 44 | const char *name; 45 | 46 | /** Docstring of the type (UTF-8 encoded); may be ``NULL``. */ 47 | const char *doc; 48 | 49 | /** 50 | * Pointer to ``NULL``-terminated array with field names of the new type 51 | * (must not be ``NULL``). 52 | */ 53 | HPyStructSequence_Field *fields; 54 | } HPyStructSequence_Desc; 55 | 56 | /** 57 | * A marker that can be used as struct sequence field name to indicate that a 58 | * field should be anonymous (i.e. cannot be accessed by a name but only by 59 | * numeric index). 60 | */ 61 | extern const char * const HPyStructSequence_UnnamedField; 62 | 63 | /** 64 | * Create a new struct sequence type from a descriptor. Instances of the 65 | * resulting type can be created with :c:func:`HPyStructSequence_New`. 66 | * 67 | * :param ctx: 68 | * The execution context. 69 | * :param desc: 70 | * The descriptor of the struct sequence type to create (must not be 71 | * ``NULL``): 72 | * 73 | * :returns: 74 | * A handle to the new struct sequence type or ``HPy_NULL`` in case of 75 | * errors. 76 | */ 77 | HPyAPI_HELPER HPy 78 | HPyStructSequence_NewType(HPyContext *ctx, HPyStructSequence_Desc *desc); 79 | 80 | /** 81 | * Creates a new instance of ``type`` initializing it with the given arguments. 82 | * 83 | * Since struct sequences are immutable objects, they need to be initialized at 84 | * instantiation. This function will create a fresh instance of the provided 85 | * struct sequence type. The type must have been created with 86 | * :c:func:`HPyStructSequence_NewType`. 87 | * 88 | * :param ctx: 89 | * The execution context. 90 | * :param type: 91 | * A struct sequence type (must not be ``HPy_NULL``). If the passed object 92 | * is not a type, the behavior is undefined. If the given type is not 93 | * appropriate, a ``TypeError`` will be raised. 94 | * :param nargs: 95 | * The number of arguments in ``args``. If this argument is not exactly the 96 | * number of fields of the struct sequence, a ``TypeError`` will be raised. 97 | * :param args: 98 | * An array of HPy handles to Python objects to be used for initializing 99 | * the struct sequence. If ``nargs > 0`` then this argument must not be 100 | * ``NULL``. 101 | * 102 | * :returns: 103 | * A new instance of ``type`` or ``HPy_NULL`` if an error occurred. 104 | */ 105 | HPyAPI_HELPER HPy 106 | HPyStructSequence_New(HPyContext *ctx, HPy type, HPy_ssize_t nargs, HPy *args); 107 | 108 | #endif /* HPY_COMMON_RUNTIME_STRUCTSEQ_H */ 109 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from .support import ExtensionCompiler, DefaultExtensionTemplate,\ 4 | PythonSubprocessRunner, HPyDebugCapture, make_hpy_abi_fixture 5 | from pathlib import Path 6 | 7 | IS_VALGRIND_RUN = False 8 | 9 | def pytest_addoption(parser): 10 | parser.addoption( 11 | "--compiler-v", action="store_true", 12 | help="Print to stdout the commands used to invoke the compiler") 13 | parser.addoption( 14 | "--subprocess-v", action="store_true", 15 | help="Print to stdout the stdout and stderr of Python subprocesses " 16 | "executed via run_python_subprocess") 17 | parser.addoption( 18 | "--dump-dir", 19 | help="Enables dump mode and specifies where to write generated test " 20 | "sources. This will then only generate the sources and skip " 21 | "evaluation of the tests.") 22 | parser.addoption( 23 | '--reuse-venv', action="store_true", 24 | help="Development only: reuse the venv for test_distutils.py instead of " 25 | "creating a new one for every test") 26 | 27 | 28 | @pytest.hookimpl(trylast=True) 29 | def pytest_configure(config): 30 | global IS_VALGRIND_RUN 31 | IS_VALGRIND_RUN = config.pluginmanager.hasplugin('valgrind_checker') 32 | config.addinivalue_line( 33 | "markers", "syncgc: Mark tests that rely on a synchronous GC." 34 | ) 35 | 36 | 37 | def pytest_runtest_setup(item): 38 | if any(item.iter_markers(name="syncgc")): 39 | if sys.implementation.name in ("pypy", "graalpy"): 40 | pytest.skip("requires synchronous garbage collector") 41 | 42 | 43 | # this is the default set of hpy_abi for all the tests. Individual files and 44 | # classes can override it. 45 | hpy_abi = make_hpy_abi_fixture('default') 46 | 47 | 48 | @pytest.fixture(scope='session') 49 | def hpy_devel(request): 50 | from hpy.devel import HPyDevel 51 | return HPyDevel() 52 | 53 | @pytest.fixture 54 | def leakdetector(hpy_abi): 55 | """ 56 | Automatically detect leaks when the hpy_abi == 'debug' 57 | """ 58 | from hpy.debug.leakdetector import LeakDetector 59 | if 'debug' in hpy_abi: 60 | with LeakDetector() as ld: 61 | yield ld 62 | else: 63 | yield None 64 | 65 | @pytest.fixture 66 | def ExtensionTemplate(): 67 | return DefaultExtensionTemplate 68 | 69 | @pytest.fixture 70 | def compiler(request, tmpdir, hpy_devel, hpy_abi, ExtensionTemplate): 71 | compiler_verbose = request.config.getoption('--compiler-v') 72 | dump_dir = request.config.getoption('--dump-dir') 73 | if dump_dir: 74 | # Test-specific dump dir in format: dump_dir/[mod_][cls_]func 75 | qname_parts = [] 76 | if request.module: 77 | qname_parts.append(request.module.__name__) 78 | if request.cls: 79 | qname_parts.append(request.cls.__name__) 80 | qname_parts.append(request.function.__name__) 81 | test_dump_dir = "_".join(qname_parts).replace(".", "_") 82 | dump_dir = Path(dump_dir).joinpath(test_dump_dir) 83 | dump_dir.mkdir(parents=True, exist_ok=True) 84 | return ExtensionCompiler(tmpdir, hpy_devel, hpy_abi, 85 | compiler_verbose=compiler_verbose, 86 | dump_dir=dump_dir, 87 | ExtensionTemplate=ExtensionTemplate) 88 | 89 | 90 | @pytest.fixture(scope="session") 91 | def fatal_exit_code(request): 92 | import sys 93 | return { 94 | "linux": -6, # SIGABRT 95 | # See https://bugs.python.org/issue36116#msg336782 -- the 96 | # return code from abort on Windows 8+ is a stack buffer overrun. 97 | # :| 98 | "win32": 0xC0000409, # STATUS_STACK_BUFFER_OVERRUN 99 | }.get(sys.platform, -6) 100 | 101 | 102 | @pytest.fixture 103 | def python_subprocess(request, hpy_abi): 104 | verbose = request.config.getoption('--subprocess-v') 105 | yield PythonSubprocessRunner(verbose, hpy_abi) 106 | 107 | 108 | @pytest.fixture() 109 | def hpy_debug_capture(request, hpy_abi): 110 | assert hpy_abi == 'debug' 111 | with HPyDebugCapture() as reporter: 112 | yield reporter 113 | -------------------------------------------------------------------------------- /hpy/devel/src/runtime/ctx_call.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hpy.h" 3 | #if defined(_MSC_VER) 4 | # include /* for alloca() */ 5 | #endif 6 | 7 | #ifndef HPY_ABI_CPYTHON 8 | // for _h2py and _py2h 9 | # include "handles.h" 10 | #endif 11 | 12 | _HPy_HIDDEN HPy 13 | ctx_CallTupleDict(HPyContext *ctx, HPy callable, HPy args, HPy kw) 14 | { 15 | PyObject *obj; 16 | if (!HPy_IsNull(args) && !HPyTuple_Check(ctx, args)) { 17 | HPyErr_SetString(ctx, ctx->h_TypeError, 18 | "HPy_CallTupleDict requires args to be a tuple or null handle"); 19 | return HPy_NULL; 20 | } 21 | if (!HPy_IsNull(kw) && !HPyDict_Check(ctx, kw)) { 22 | HPyErr_SetString(ctx, ctx->h_TypeError, 23 | "HPy_CallTupleDict requires kw to be a dict or null handle"); 24 | return HPy_NULL; 25 | } 26 | if (HPy_IsNull(kw)) { 27 | obj = PyObject_CallObject(_h2py(callable), _h2py(args)); 28 | } 29 | else if (!HPy_IsNull(args)){ 30 | obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw)); 31 | } 32 | else { 33 | // args is null, but kw is not, so we need to create an empty args tuple 34 | // for CPython's PyObject_Call 35 | HPy *items = NULL; 36 | HPy empty_tuple = HPyTuple_FromArray(ctx, items, 0); 37 | obj = PyObject_Call(_h2py(callable), _h2py(empty_tuple), _h2py(kw)); 38 | HPy_Close(ctx, empty_tuple); 39 | } 40 | return _py2h(obj); 41 | } 42 | 43 | #if PY_VERSION_HEX < 0x03090000 44 | # define PyObject_Vectorcall _PyObject_Vectorcall 45 | #endif 46 | 47 | _HPy_HIDDEN HPy 48 | ctx_Call(HPyContext *ctx, HPy h_callable, const HPy *h_args, size_t nargs, HPy h_kwnames) 49 | { 50 | PyObject *kwnames; 51 | size_t n_all_args; 52 | 53 | if (HPy_IsNull(h_kwnames)) { 54 | kwnames = NULL; 55 | n_all_args = nargs; 56 | } else { 57 | kwnames = _h2py(h_kwnames); 58 | assert(kwnames != NULL); 59 | assert(PyTuple_Check(kwnames)); 60 | n_all_args = nargs + PyTuple_GET_SIZE(kwnames); 61 | assert(n_all_args >= nargs); 62 | } 63 | 64 | /* Since we already allocate a fresh args array, we make it one element 65 | larger and set PY_VECTORCALL_ARGUMENTS_OFFSET to avoid further 66 | allocations from CPython. */ 67 | PyObject **args = (PyObject **) alloca((n_all_args + 1) * sizeof(PyObject *)); 68 | for (size_t i = 0; i < n_all_args; i++) { 69 | args[i+1] = _h2py(h_args[i]); 70 | } 71 | 72 | return _py2h(PyObject_Vectorcall(_h2py(h_callable), args+1, 73 | nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames)); 74 | } 75 | 76 | #if PY_VERSION_HEX < 0x03090000 77 | # undef PyObject_Vectorcall 78 | #endif 79 | 80 | _HPy_HIDDEN HPy 81 | ctx_CallMethod(HPyContext *ctx, HPy h_name, const HPy *h_args, size_t nargs, 82 | HPy h_kwnames) 83 | { 84 | PyObject *result, *kwnames; 85 | size_t n_all_args; 86 | 87 | if (HPy_IsNull(h_kwnames)) { 88 | kwnames = NULL; 89 | n_all_args = nargs; 90 | } else { 91 | kwnames = _h2py(h_kwnames); 92 | assert(kwnames != NULL); 93 | assert(PyTuple_Check(kwnames)); 94 | n_all_args = nargs + PyTuple_GET_SIZE(kwnames); 95 | assert(n_all_args >= nargs); 96 | } 97 | 98 | /* Since we already allocate a fresh args array, we make it one element 99 | larger and set PY_VECTORCALL_ARGUMENTS_OFFSET to avoid further 100 | allocations from CPython. */ 101 | PyObject **args = (PyObject **) alloca( 102 | (n_all_args + 1) * sizeof(PyObject *)); 103 | for (size_t i = 0; i < n_all_args; i++) { 104 | args[i+1] = _h2py(h_args[i]); 105 | } 106 | 107 | #if PY_VERSION_HEX < 0x03090000 108 | PyObject *method = PyObject_GetAttr(args[1], _h2py(h_name)); 109 | if (method == NULL) 110 | return HPy_NULL; 111 | result = _PyObject_Vectorcall(method, &args[2], 112 | (nargs-1) | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); 113 | Py_DECREF(method); 114 | #else 115 | result = PyObject_VectorcallMethod(_h2py(h_name), args+1, 116 | nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); 117 | #endif 118 | return _py2h(result); 119 | } 120 | -------------------------------------------------------------------------------- /test/test_hpydict.py: -------------------------------------------------------------------------------- 1 | from .support import HPyTest 2 | 3 | class TestDict(HPyTest): 4 | 5 | def test_Check(self): 6 | mod = self.make_module(""" 7 | HPyDef_METH(f, "f", HPyFunc_O) 8 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 9 | { 10 | if (HPyDict_Check(ctx, arg)) 11 | return HPy_Dup(ctx, ctx->h_True); 12 | return HPy_Dup(ctx, ctx->h_False); 13 | } 14 | @EXPORT(f) 15 | @INIT 16 | """) 17 | class MyDict(dict): 18 | pass 19 | 20 | assert mod.f({}) is True 21 | assert mod.f([]) is False 22 | assert mod.f(MyDict()) is True 23 | 24 | def test_New(self): 25 | mod = self.make_module(""" 26 | HPyDef_METH(f, "f", HPyFunc_NOARGS) 27 | static HPy f_impl(HPyContext *ctx, HPy self) 28 | { 29 | return HPyDict_New(ctx); 30 | } 31 | @EXPORT(f) 32 | @INIT 33 | """) 34 | assert mod.f() == {} 35 | 36 | def test_set_item(self): 37 | mod = self.make_module(""" 38 | HPyDef_METH(f, "f", HPyFunc_O) 39 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 40 | { 41 | HPy dict = HPyDict_New(ctx); 42 | if (HPy_IsNull(dict)) 43 | return HPy_NULL; 44 | HPy val = HPyLong_FromLong(ctx, 1234); 45 | if (HPy_SetItem(ctx, dict, arg, val) == -1) 46 | return HPy_NULL; 47 | HPy_Close(ctx, val); 48 | return dict; 49 | } 50 | @EXPORT(f) 51 | @INIT 52 | """) 53 | assert mod.f('hello') == {'hello': 1234} 54 | 55 | def test_get_item(self): 56 | mod = self.make_module(""" 57 | HPyDef_METH(f, "f", HPyFunc_O) 58 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 59 | { 60 | HPy key = HPyUnicode_FromString(ctx, "hello"); 61 | if (HPy_IsNull(key)) 62 | return HPy_NULL; 63 | HPy val = HPy_GetItem(ctx, arg, key); 64 | HPy_Close(ctx, key); 65 | if (HPy_IsNull(val)) { 66 | HPyErr_Clear(ctx); 67 | return HPy_Dup(ctx, ctx->h_None); 68 | } 69 | return val; 70 | } 71 | @EXPORT(f) 72 | @INIT 73 | """) 74 | assert mod.f({'hello': 1}) == 1 75 | assert mod.f({}) is None 76 | 77 | def test_keys(self): 78 | import pytest 79 | mod = self.make_module(""" 80 | HPyDef_METH(f, "f", HPyFunc_O) 81 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 82 | { 83 | HPy h_dict = HPy_Is(ctx, arg, ctx->h_None) ? HPy_NULL : arg; 84 | return HPyDict_Keys(ctx, h_dict); 85 | } 86 | @EXPORT(f) 87 | @INIT 88 | """) 89 | 90 | class SubDict(dict): 91 | def keys(self): 92 | return [1, 2, 3] 93 | assert mod.f({}) == [] 94 | assert mod.f({'hello': 1}) == ['hello'] 95 | assert mod.f(SubDict(hello=1)) == ['hello'] 96 | with pytest.raises(SystemError): 97 | mod.f(None) 98 | with pytest.raises(SystemError): 99 | mod.f(42) 100 | 101 | def test_copy(self): 102 | import pytest 103 | mod = self.make_module(""" 104 | HPyDef_METH(f, "f", HPyFunc_O) 105 | static HPy f_impl(HPyContext *ctx, HPy self, HPy arg) 106 | { 107 | HPy h_dict = HPy_Is(ctx, arg, ctx->h_None) ? HPy_NULL : arg; 108 | return HPyDict_Copy(ctx, h_dict); 109 | } 110 | @EXPORT(f) 111 | @INIT 112 | """) 113 | dicts = ({}, {'hello': 1}) 114 | for d in dicts: 115 | d_copy = mod.f(d) 116 | assert d_copy == d 117 | assert d_copy is not d 118 | with pytest.raises(SystemError): 119 | mod.f(None) 120 | with pytest.raises(SystemError): 121 | mod.f(42) 122 | --------------------------------------------------------------------------------