Skip to main content

hpy 0.0.4: Third public release

HPy 0.0.4 is out! The third official HPy release comes with many new features and was again made available on PyPI.

Major highlights of the release are a bunch of new API functions (e.g. HPyErr_ExceptionMatches, HPyErr_WarnEx, HPy_Contains, and more), Python 3.10 support, better support for native fields (HPyField) and global variables (HPyGlobal), new debug mode features (detect invalid raw data pointer usage, detect invalid closing of argument handles, detect return of invalid handles).

Great news too is that we are now able to provide two more non-trivial projects that have been (partially) migrated to HPy. This is, Kiwisolver and Matplotlib.

What is HPy?

HPy provides a new API for extending Python in C. In other words, you use #include <hpy.h> instead of #include <Python.h>. For more info, look at the official documentation.

Installation

HPy 0.0.4 is best tested on Linux systems but there is also initial support for Windows (both x86_64). For CPython, you need to install it manually, using pip:

$ pip install hpy==0.0.4

PyPy and GraalPython already come with intrinsic HPy support, so no installation is necessary. HPy 0.0.4 will be included in the next release of both. In the meantime, you can download a nightly or dev build:

To check the version of HPy which is shipped with those, you can either use pip or hpy.universal.get_version():

$ pypy -m pip show hpy
Name: hpy
Version: 0.0.4
...

$ graalpython -m pip show hpy
Name: hpy
Version: 0.0.4
...

$ pypy -c 'import hpy.universal; print(hpy.universal.get_version()[0])'
0.0.4

$ graalpython -c 'import hpy.universal; print(hpy.universal.get_version()[0])'
0.0.4

API

We are constantly working on the HPy API and keep adding functions that are missing. We've added following API functions to the new release:

  • HPyErr_SetFromErrnoWithFilename, HPyErr_SetFromErrnoWithFilenameObjects
  • HPyErr_ExceptionMatches
  • HPyErr_WarnEx
  • HPyErr_WriteUnraisable
  • HPy_Contains
  • HPyLong_AsVoidPtr
  • HPyLong_AsDouble
  • HPyUnicode_AsASCIIString, HPyUnicode_DecodeASCII
  • HPyUnicode_AsLatin1String, HPyUnicode_DecodeLatin1
  • HPyUnicode_DecodeFSDefault, HPyUnicode_DecodeFSDefaultAndSize
  • HPyUnicode_ReadChar

For an overview of the current API, please refer to the public API declaration in public_api.h, which is used to autogenerate parts of the HPy code and is a reliable list of all the supported functions. Also have a look at additional helpers in inline_helpers.h.

Warning

The HPy API is still considered in alpha status and it's subject to change between versions.

Debug Mode

We again improved HPy's debug mode and added following new features:

Enable Debug Mode via Environment Variable

The debug mode can now be enabled using environment variable HPY_DEBUG. It is possible to enable the debug mode for all HPy extensions or it is also possible to enable it just for certain extensions by enumerating them.

Example:

$ # enable debug mode for all HPy extensions
$ HPY_DEBUG=1 python3 my_application.py

$ # enable debug mode just for ujson_hpy and piconumpy_hpy
$ HPY_DEBUG=ujson_hpy,piconumpy_hpy python3 my_application.py

Detect Invalid Use of Raw Data Pointers

Some API functions return a raw data pointer from an object. For example:

const char* HPyUnicode_AsUTF8AndSize(HPyContext *ctx, HPy h, HPy_ssize_t *size)

returns a raw data pointer to the UTF8 representation of a Python unicode object. HPy doesn't expose the internal representation of the unicode object, so the Python implementation may use an arbitrary internal representation. This means that the UTF8 representation is just temporarily created for this API call and so the raw data must be released at some point. The contract here is that the raw data pointer is valid as long as the corresponding handle is valid.

Example:

#include <string.h>

static const char *s_hello_world = "Hello, World!";

static const char * foo(HPyContext *ctx)
{
    HPy h_unicode = HPyUnicode_FromString(ctx, s_hello_word);
    HPy_ssize_t size;
    const char *res = HPyUnicode_AsUTF8AndSize(ctx, h_unicode, &size);

    /* closing 'h_unicode' is, of course, correct */
    HPy_Close(ctx, h_unicode);

    /* raw data pointer 'res' may have become invalid when closing
       'h_unicode' */
    return res;
}

static int bar(HPyContext *ctx)
{
    const char *s = foo(ctx);

    /* accessing 's' will cause a fatal error in debug mode (on supported
    systems) */
    return strcmp(s, s_hello_world) == 0;
}

It is easy to forget about this resriction and if the raw data pointer is used after the handle was closed, it may point to garbage. If the debug mode is enabled, it will make the underlying memory inaccessible and every access to the pointer will then cause a crash of the application. This is currently only implemented for Linux systems. We use a different strategy on other systems and fill the pointer with some marker bytes that make it easy to detect.

Detect Incorrect Closing of Argument Handles

HPy functions that are called from Python receive handles that are owned by the caller. This means that those handles must not be closed by the callee but it is, of course, possible to erroneously call HPy_Close on them. For example:

HPyDef_METH(foo, "foo", foo_impl, HPyFunc_O, .doc="closing argument")
static HPy foo_impl(HPyContext *ctx, HPy self, HPy arg)
{
    // error: 'arg' is owned by the caller
    HPy_Close(ctx, arg);
    return HPy_Dup(ctx, ctx->h_None);
}

Detect Invalid Handles Returned from Function

A common problem when returning handles is that the author may easily forget to create a new handle. The debug mode now detects situations like the following:

HPyDef_METH(foo, "foo", foo_impl, HPyFunc_NOARGS, .doc="returns arg w/o dupping it")
static HPy foo_impl(HPyContext *ctx, HPy self)
{
    // should be: return HPy_Dup(ctx, self);
    return self;
}

Examples

Besides the known examples, this is HPy's "proof of concept" package, ultrajson-hpy, piconumpy, we are excited to present two new packages we have migrated to HPy:

  • Kiwi is an efficient C++ implementation of the Cassowary constraint solving algorithm.

  • Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. Since Matplotlib also has a dependency to NumPy, the migration is not fully finished but luckily, HPy provides the legacy compatibility API such that we can still call legacy C API functions from HPy.

We are still cleaning these ports up and will write another blog post about the ports and open them for discussion with the project owners.

Comments