I would like to create a basic inheritance structure based on Halide::Generator in Halide/C++, so as to avoid duplicated code.
The idea is to have an abstract base generator class that owns a pure virtual function. Moreover, each derived class should have a specific input parameter, not available in the base class.
In normal C++ this is quite straightforward, but as Halide is a DSL that "generates code" before linking and compiling, things may get a little bit messy.
My current Halide implementation is all in a single file:
my_generators.cpp
#include "Halide.h"
#include <stdio.h>
using namespace Halide;
class Base : public Halide::Generator<Base> {
public:
Input<Buffer<float>> input{"input", 2};
Output<Buffer<float>> output{"brighter", 2};
Var x, y;
virtual Func process(Func input) = 0;
virtual void generate() {
output = process(input);
output.vectorize(x, 16).parallel(y);
}
};
class DerivedGain : public Base {
public:
Input<float> gain{"gain"};
Func process (Func input) override{
Func result("result");
result(x,y) = input(x,y) * gain;
return result;
}
};
class DerivedOffset : public Base{
public:
Input<float> offset{"offset"};
Func process (Func input) override{
Func result("result");
result(x,y) = input(x,y) + offset;
return result;
}
};
HALIDE_REGISTER_GENERATOR(DerivedGain, derived_gain)
HALIDE_REGISTER_GENERATOR(DerivedOffset, derived_offset)
In order to compile it, I have used this CMakeLists file:
cmake_minimum_required(VERSION 3.16)
project(HalideExample)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
find_package(Halide REQUIRED)
add_executable(my_generators my_generators.cpp)
target_include_directories(my_generators PUBLIC ${HALIDE_ROOT}/include)
target_link_libraries(my_generators PRIVATE Halide::Generator)
add_halide_library(derived_gain FROM my_generators)
add_halide_library(derived_offset FROM my_generators)
I have been using the pre-built version Halide-13.0.1-x86-64-linux here
But during compilation, it launches an error suggesting that class Base
was being instantiated (which I do not need to happen) :
In file included from <path_to_project>/my_generators.cpp:2:
<path_to_halide>/include/Halide.h: In instantiation of ‘static std::unique_ptr<_Tp> Halide::Generator<T>::create(const Halide::GeneratorContext&) [with T = Base]’:
<path_to_halide>/include/Halide.h:26640:14: required from ‘static std::unique_ptr<_Tp> Halide::Generator<T>::create(const Halide::GeneratorContext&, const string&, const string&) [with T = Base; std::string = std::__cxx11::basic_string<char>]’
<path_to_project>/my_generators.cpp:53:1: required from here
<path_to_halide>/include/Halide.h:26631:37: error: invalid new-expression of abstract class type ‘Base’
26631 | auto g = std::unique_ptr<T>(new T());
| ^~~~~~~
<path_to_project>/my_generators.cpp:7:7: note: because the following virtual functions are pure within ‘Base’:
7 | class Base : public Halide::Generator<Base> {
| ^~~~
<path_to_project>/my_generators.cpp:15:18: note: ‘virtual Halide::NamesInterface::Func Base::process(Halide::NamesInterface::Func)’
15 | virtual Func process(Func input) = 0;
If instead of using a virtual
function I implement it in the Base class like so:
class Base : public Halide::Generator<Base> {
public:
Input<Buffer<float>> input{"input", 2};
Output<Buffer<float>> output{"brighter", 2};
Var x, y;
// Func process(Func input);
Func process (Func input){
Func result("result");
result(x,y) = input(x,y);
return result;
}
virtual void generate() {
output = process(input);
output.vectorize(x, 16).parallel(y);
}
};
Then everything compiles, but the object and header files with the generated code have the wrong function signatures (noticeable as there are missing gain/offset parameters):
derived_gain.h:
int derived_gain(struct halide_buffer_t *_input_buffer, struct halide_buffer_t *_result_buffer);
derived_offset.h:
int derived_offset(struct halide_buffer_t *_input_buffer, struct halide_buffer_t *_result_buffer);
Therefore, I would like to know which mistake I am introducing in the class definitions and how to solve it.
You could turn the base class into a template:
template<class T>
class Base : public Halide::Generator<T> {
and then re-export the Input
and Output
names... (I'm not enough of a C++ guru to understand why this is necessary):
// In class Base:
template <typename T2>
using Input = typename Halide::Generator<T>::template Input<T2>;
template <typename T2>
using Output = typename Halide::Generator<T>::template Output<T2>;
Then the remaining changes are just:
class DerivedGain : public Base<DerivedGain> { ... };
class DerivedOffset : public Base<DerivedOffset> { ... };
This seemed to work for me.
Also, you probably do not need this line in your CMakeLists.txt (I didn't):
target_include_directories(my_generators PUBLIC ${HALIDE_ROOT}/include)
Our package doesn't set HALIDE_ROOT
, and linking to Halide::Generator
already sets up the include paths correctly, anyway.