Skip to content
Effortlessly write inline C functions in Python
Python Dockerfile
Branch: master
Clone or download

Latest commit

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
inlinec Initial commit Feb 2, 2020
tests Initial commit Feb 2, 2020
.gitignore Initial commit Feb 2, 2020
Dockerfile Install via setup.py in Dockerfile Feb 2, 2020
LICENSE.txt Add LICENSE.txt Feb 2, 2020
MANIFEST.in Initial commit Feb 2, 2020
README.md
inlinec.pth Initial commit Feb 2, 2020
pytest.ini Initial commit Feb 2, 2020
setup.cfg Add setup.cfg Feb 2, 2020
setup.py Fix typo in setup.py Feb 2, 2020

README.md

Inline C

Effortlessly write inline C functions in Python source code

# coding: inlinec
from inlinec import inlinec

@inlinec
def Q_rsqrt(number):
    float Q_rsqrt( float number )
    {
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;                       // evil floating point bit level hacking
        i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration

        return y;
    }

print(Q_rsqrt(1.234))

Inlinec supports gnu-specific c extensions, so you're likely to have reasonable success #includeing glibc headers.

Inspired by Pyxl

How does this work?

Python has a mechanism for creating custom codecs, which given an input token stream, produce an output token stream. Inlinec consumes the entire token stream, runs a fault-tolerant parser on it (parso), finds which function nodes are annotated with an @inlinec decorator, creates a ctypes wrapper for the content of the function, and replaces the function body with a call to the ctypes wrapper. The import for the wrapper is lifted to the top of the file. Once this transformation has been made, the source code is re-tokenized and the Python interpreter only sees the transformed source. So a function like this:

@inlinec
def test():
    #include<stdio.h>
    void test() {
        printf("Hello, world");
    }

Gets turned into:

from test_8281231239129310 import lib as test_8281231239129310_lib, ffii as test_8281231239129310_ffi

@inlinec
def test():
    return test_8281231239129310_lib.test()

In theory, this allows inline c functions to be called with a one-time compilation overhead and the same performance characteristics as ctypes -- the underlying FFI library.

Limitations

Note: This is just a proof of concept

  • Passing pointers to C functions does not currently work (aside from strings)
  • Shells out to gcc -E to preprocess the source code
  • Compilation is not cached and takes a long time every run
  • Compilation pollutes the current directory with .so, .o, .c files
  • The source file is parsed multiple times unnecessarily
  • Many more

Installation

Inlinec requires a C compiler to be installed on the system (tested with GCC and Clang), as well as the python development libraries to be installed (python3-dev). To play around with it in a container you can use the provided Dockerfile, just run docker build, exec into a shell in the container, and you have a working installation of inlinec.

> docker build -t inlinec . && docker run -it inlinec bash
You can’t perform that action at this time.