Running the compiled pipeline#

// Halide tutorial lesson 10: AOT compilation: running the compiled pipeline

// Before reading this file, see lesson_10_aot_compilation_generate.cpp

// This is the code that actually uses the Halide pipeline we've
// compiled. It does not depend on libHalide, so we won't be including
// Halide.h.
//
// Instead, it depends on the header file that lesson_10_generate
// produced when we ran it:
#include "lesson_10_halide.h"

// We want to continue to use our Halide::Buffer with AOT-compiled
// code, so we explicitly include it. It's a header-only class, and
// doesn't require libHalide.
#include "HalideBuffer.h"

#include <cstdio>

int main() {
    // Have a look in the header file above (it won't exist until you've run
    // lesson_10_generate). At the bottom is the signature of the function we generated:

    // int brighter(halide_buffer_t *_input_buffer, uint8_t _offset, halide_buffer_t *_brighter_buffer);

    // The ImageParam inputs have become pointers to "halide_buffer_t"
    // structs. This is struct that Halide uses to represent arrays of
    // data.  Unless you're calling the Halide pipeline from pure C
    // code, you don't want to use it
    // directly. Halide::Runtime::Buffer is a simple wrapper around
    // halide_buffer_t that will implicitly convert to a
    // halide_buffer_t *. We will pass Halide::Runtime::Buffer objects
    // in those slots.

    // The Halide::Buffer class we have been using in JIT code is in
    // fact just a shared pointer to the simpler
    // Halide::Runtime::Buffer class. They share the same API.

    // Finally, the return value of "brighter" is an error code. It's
    // zero on success.

    // Let's make a buffer for our input and output.
    Halide::Runtime::Buffer<uint8_t> input(640, 480), output(640, 480);

    // Halide::Runtime::Buffer also has constructors that wrap
    // existing data instead of allocating new memory. Use these if
    // you have your own Image type that you want to use.

    int offset = 5;
    int error = brighter(input, offset, output);

    if (error != 0) {
        printf("Halide returned an error: %d\n", error);
        return -1;
    }

    // Now let's check the filter performed as advertised. It was
    // supposed to add the offset to every input pixel.
    for (int y = 0; y < 480; y++) {
        for (int x = 0; x < 640; x++) {
            uint8_t input_val = input(x, y);
            uint8_t output_val = output(x, y);
            uint8_t correct_val = input_val + offset;
            if (output_val != correct_val) {
                printf("output(%d, %d) was %d instead of %d\n",
                       x, y, output_val, correct_val);
                return -1;
            }
        }
    }

    // Everything worked!
    printf("Success!\n");
    return 0;
}
# Before reading this file, see lesson_10_aot_compilation_generate.py

# This is the code that actually uses the Halide pipeline we've
# compiled. It does not depend on libHalide, so we won't do
# "import halide".
#
# Instead, it depends on the header file that lesson_10_generate
# produced when we ran it:
import lesson_10_halide
import numpy as np


def main():
    # Have a look at the generated files above (they won't exist until you've run
    # lesson_10_generate): lesson_10_halide.py.cpp, lesson_10_halide.h
    #
    # In the header file, the generated function is represented like this:
    # int lesson_10_halide(halide_buffer_t*, uint8_t, halide_buffer_t*);
    #
    # lesson_10_halide.py.cpp creates a Python wrapper around this function.
    # Buffers are converted using the Python buffer API:
    #
    # https://docs.python.org/2/c-api/buffer.html
    # https://docs.python.org/3/c-api/buffer.html
    #
    # In other words, you can pass numpy arrays directly to the generated
    # code.

    # Let's make some input data to test with. Note that when a numpy array is
    # passed to the generated code, its axes are always reversed.
    input = np.empty((640, 480), dtype=np.uint8)
    for y in range(480):
        for x in range(640):
            input[x, y] = (x ^ (y + 1)) & 0xFF

    # And the memory where we want to write our output:
    output = np.empty((640, 480), dtype=np.uint8)

    offset_value = 5

    lesson_10_halide.lesson_10_halide(input, offset_value, output)

    # Now let's check the filter performed as advertised. It was
    # supposed to add the offset to every input pixel.
    correct_val = np.empty((1), dtype=np.uint8)
    for y in range(480):
        for x in range(640):
            input_val = input[x, y]
            output_val = output[x, y]
            correct_val[0] = input_val
            # we add over a uint8 value (will properly model overflow)
            correct_val[0] += offset_value
            assert output_val == correct_val[0], (
                f"output({x}, {y}) was {output_val} instead of {correct_val}"
            )

    # Everything worked!
    print("Success!")
    return 0


if __name__ == "__main__":
    main()