Part 6 涵蓋了 Tuple, Type 與 Generator
在早期版本的 Halide Tutorial 只有到此為止
而後續的版本加入了一些實務上實用的教學
Halide Tutorial 非官方中譯 - Part 1
Halide Tutorial 非官方中譯 - Part 2
Halide Tutorial 非官方中譯 - Part 3
Halide Tutorial 非官方中譯 - Part 4
Halide Tutorial 非官方中譯 - Part 5
==
// 至今 Funcs 都用以對其範圍內每一個點計算單一純量數值(如同下列的一個)
Func single_valued;
Var x, y;
single_valued(x, y) = x + y;
// 撰寫回傳多個數值的集合的一個方法是增加集合額外的索引
// 這通常用以處理彩色影像.
// 像是下列的 Func 表示了對於每個 x, y 以 c 作索引的三數值之集合.
Func color_image;
Var c;
color_image(x, y, c) = select(c == 0, 245, // Red value
c == 1, 42, // Green value
132); // Blue value
// 這方法通常相當方便, 因為這讓 Func 對每個集合項目都同等地處理的使用上變簡單:
Func brighter;
brighter(x, y, c) = color_image(x, y, c) + 10;
// 然而這方法也有不方便的3個原因
//
// 1) Funcs 在無限制的區域上定義,
// 因此 Func 能夠被用以存取像是 color_image(x, y, -17) 這樣無意義以及可能代表著錯誤的位置.
//
// 2) 需要選擇, 若沒有限制或展開, 這能夠對效能產生影響:
// brighter.bound(c, 0, 3).unroll(c);
//
// 3) 使用這方法, 集合內所有的數值必須是相同型別.
// 上述兩種方法僅只是不方便, 這點是個困難的限制造成無法以此方式表達特定事務.
// 亦是有可能以 Funcs 的集合來表示:
Func func_array[3];
func_array[0](x, y) = x + y;
func_array[1](x, y) = sin(x);
func_array[2](x, y) = cos(y);
// 使用 Func 集合來表示的確避免了上述的三個問題, 但是產生了新的惱人問題.
// 由於各自為分別的 Funcs, 因此難以在單一的 x, loop 之中一同排程與計算.
// 一個第3點的替代方案是定義一個 Func 去計算 Tuple 而非 Expr.
// 一個 Tuple 是固定大小的 Expr 集合. 每個 Tuple 中的 Expr 可能有著不同的型別.
// 接著的函數計算一個整數數值 (x+y) 以及浮點數數值 (sin(x*y)).
Func multi_valued;
multi_valued(x, y) = Tuple(x + y, sin(x * y));
// 實現一個以 Tuple 為回傳型態的 Func, 會回傳一個 Buffer 的集合.
// 這物件被稱為 Realization. 等義於存放 Buffer 物件的 std::vector:
{
Realization r = multi_valued.realize(80, 60);
assert(r.size() == 2);
Buffer im0 = r[0];
Buffer im1 = r[1];
assert(im0(30, 40) == 30 + 40);
assert(im1(30, 40) == sinf(30 * 40));
}
// 所有 Tuple 的內容都是在同一個範圍以相同的 loop 中所計算,
// 但存入不同的配置記憶體區域. 等義的 C++ code 如下:
{
int multi_valued_0[80*60];
float multi_valued_1[80*60];
for (int y = 0; y < 80; y++) {
for (int x = 0; x < 60; x++) {
multi_valued_0[x + 60*y] = x + y;
multi_valued_1[x + 60*y] = sinf(x*y);
}
}
}
// 當以使用 AOT 方式編譯時,
// 以 Tuple 為回傳值的 Func 會將計算填入多個不同的輸出 buffer_t 結構
// 這些會依序而形成下列函式型別:
// int multi_valued(...input buffers and params...,
// buffer_t *output_1, buffer_t *output_2);
// 如同先前所做的, 能夠藉由傳入多個 Exprs 到 Tuple 建構子來建構一個 Tuple.
// 但或許能優雅地藉助 C++11 初始化列表(initializer lists) 的特性,
// 直接將 Exprs 以大括號包起來:
Func multi_valued_2;
multi_valued_2(x, y) = {x + y, sin(x*y)};
// 並不能如同 Exprs 來呼叫多回傳值 Func. 下列是個語法錯誤:
// Func consumer;
// consumer(x, y) = multi_valued_2(x, y) + 10;
// 因此必須以中括號索引 Tuple 中不同的 Expr:
Expr integer_part = multi_valued_2(x, y)[0];
Expr floating_part = multi_valued_2(x, y)[1];
Func consumer;
consumer(x, y) = {integer_part + 10, floating_part + 10.0f};
// Tuple 歸納.
{
// Tuples 在歸納中特別有用, 這使得在範圍中歸納得以維持複雜的狀態.
// 最簡單的例子是 argmax.
// 首先建立一個後續將用於 argmax 的 buffer
Func input_func;
input_func(x) = sin(x);
Buffer input = input_func.realize(100);
// 接著定義有著兩個數值的 Tuple 用以追蹤最大值與其數值.
Func arg_max;
// 純定義
arg_max() = {0, input(0)};
// 更新定義
RDom r(1, 99);
Expr old_index = arg_max()[0];
Expr old_max = arg_max()[1];
Expr new_index = select(old_max < input(r), r, old_index);
Expr new_max = max(input(r), old_max);
arg_max() = {new_index, new_max};
// 等義 C++ code:
int arg_max_0 = 0;
float arg_max_1 = input(0);
for (int r = 1; r < 100; r++) {
int old_index = arg_max_0;
float old_max = arg_max_1;
int new_index = old_max < input(r) ? r : old_index;
float new_max = std::max(input(r), old_max);
// 在 tuple 更新定義中, 所有的載入與計算都在存入前完成,
// 所以所有 Tuple 元素都是透過遞迴呼叫相同 Func 自動更新的。
arg_max_0 = new_index;
arg_max_1 = new_max;
}
// 驗證 Halide 與 C++ 找到相同的最大值與索引值
{
Realization r = arg_max.realize();
Buffer r0 = r[0];
Buffer r1 = r[1];
assert(arg_max_0 == r0(0));
assert(arg_max_1 == r1(0));
}
// 如同 sum, product, maximum 與 minimum
// Halide 提供的 argmax 與 argmin 是內建的歸納函式
// 其在歸納的範圍內回傳對應於其值與各點的本身數值的 Tuple
// 在這例子中, 其回傳找到的第一個數值.
// 在後續將使用其中之一
}
// 使用者自定義的 Tuples
{
// Tuples 也是個表示複雜物件的方式, 像是複數.
// 定義一個能雙向轉換 Tuple 的物件,
// 是一個以使用者自定型別延伸 Halide 型別系統的方式.
struct Complex {
Expr real, imag;
// 以 Tuple 來建構
Complex(Tuple t) : real(t[0]), imag(t[1]) {}
// 以 Expr Pair 來建構
Complex(Expr r, Expr i) : real(r), imag(i) {}
// 以視為 Tuple 的 Func 呼叫來建構
Complex(FuncRef t) : Complex(Tuple(t)) {}
// 轉換到 Tuple
operator Tuple() const {
return {real, imag};
}
// 複數加法
Complex operator+(const Complex &other) const {
return {real + other.real, imag + other.imag};
}
// 複數乘法
Complex operator*(const Complex &other) const {
return {real * other.real - imag * other.imag,
real * other.imag + imag * other.real};
}
// 複數平方
Expr magnitude_squared() const {
return real * real + imag * imag;
}
// 能夠在此實作其他複數運算, 但上述對於此範例已足夠
};
// 接著使用複數結構來計算曼德博集合(Mandelbrot set)
Func mandelbrot;
// 在 Func 中依照 x, y 座標來初始複數數值
Complex initial(x/15.0f - 2.5f, y/6.0f - 2.0f);
// 純定義.
Var t;
mandelbrot(x, y, t) = Complex(0.0f, 0.0f);
// 使用一個更新定義來處理 12 個步驟
RDom r(1, 12);
Complex current = mandelbrot(x, y, r-1);
// 下行使用到先前定義的複數乘法與加法
mandelbrot(x, y, r) = current*current + initial;
// 使用另一個 Tuple 歸納來計算第一個能逃脫半徑為 4 的圓的迭代數
// 這能以一個 boolean 型別的 argmin 表示 -
// - 需要第一個回傳為 false 的索引.(將 false 認為比 true 小)
// 而 argmax 將會回傳第一個其表示值為 true 的索引.
Expr escape_condition = Complex(mandelbrot(x, y, r)).magnitude_squared() < 16.0f;
Tuple first_escape = argmin(escape_condition);
// 這裡僅需要索引, 並不需要數值, 然而 argmin 回傳兩者,
// 因此將以中括號方式自 argmin 的 Tuple 取出代表索引的 Expr
Func escape;
escape(x, y) = first_escape[0];
// 實現此 pipeline 並印出結果.
Buffer result = escape.realize(61, 25);
const char *code = " .:-~*={}&%#@";
for (int y = 0; y < result.height(); y++) {
for (int x = 0; x < result.width(); x++) {
printf("%c", code[result(x, y)]);
}
printf("\n");
}
}
==
// 所有的 Exprs 有著純量型別, 另外所有 Funcs 計算一或多個純量型別.
// 在 Halide 中純量型別為寬度不同的無號整數, 寬度相同的有號整數,
// 單精準與倍精準的浮點數以及黑盒子的 handle (等同 void *)
// 以下包含了所有合法型別:
Type valid_halide_types[] = {
UInt(8), UInt(16), UInt(32), UInt(64),
Int(8), Int(16), Int(32), Int(64),
Float(32), Float(64), Handle()
};
// 建構與檢查型別:
{
// 能夠以程式方式確認 Halide 型別的特性
// 這對撰寫 C++ 有著 Expr 參數的函數, 又想要確認參數型別時很有用:
assert(UInt(8).bits() == 8);
assert(Int(8).is_int());
// 亦能夠以程式方式建立如同函式或其他型別的型別
Type t = UInt(8);
t = t.with_bits(t.bits() * 2);
assert(t == UInt(16));
// 或是以 C++ 純量型別來建立型別
assert(type_of<float>() == Float(32));
// Type 結構也能夠表示 vector 型別, 但這被保留僅供 Halide 內部使用.
// 必須使用 Func::vectorize 來向量化程式碼, 而非直接建構向量的表示方式.
// 程式上操作低階 Halide 程式碼時可能會遭遇向量型別,
// 但這是進階的題目.(見 Func::add_custom_lowering_pass).
// 能夠查詢任何 Halide Expr 的型別.
// 一個代表著 Var 的 Expr 有著 Int(32) 的型別:
assert(Expr(x).type() == Int(32));
// 在 Halide 中多數的超越函數將輸入轉換為 Float(32) 也回傳 Float(32):
assert(sin(x).type() == Float(32));
// 能夠藉由使用轉換操作將 Expr 型別轉變為另一個型別:
assert(cast(UInt(8), x).type() == UInt(8));
// 這也是使用 C++ 型別的 template 型式
assert(cast<uint8_t>(x).type() == UInt(8));
// 也能夠查詢任何已定義的 Func 所產生的型別
Func f1;
f1(x) = cast<uint8_t>(x);
assert(f1.output_types()[0] == UInt(8));
Func f2;
f2(x) = {x, sin(x)};
assert(f2.output_types()[0] == Int(32) &&
f2.output_types()[1] == Float(32));
}
// 型別提升規則(type promotion rules)
{
// 當合併 Expr 與不同的型別時(像是 '+', '*' 等等),
// Halide 有著型別提升系統的規則.
// 與 C 的規則不同, 為了示範將會產生每個型別的 Expr
Var x;
Expr u8 = cast<uint8_t>(x);
Expr u16 = cast<uint16_t>(x);
Expr u32 = cast<uint32_t>(x);
Expr u64 = cast<uint64_t>(x);
Expr s8 = cast<int8_t>(x);
Expr s16 = cast<int16_t>(x);
Expr s32 = cast<int32_t>(x);
Expr s64 = cast<int64_t>(x);
Expr f32 = cast<float>(x);
Expr f64 = cast<double>(x);
// 規則如下, 並且以列出的次序所套用
// 1) 對 Handle() 的 Expr 作型別轉換或是做計算都是錯誤
// 2) 若型別相同, 不會有轉換發生
for (Type t : valid_halide_types) {
// Skip the handle type.
if (t.is_handle()) continue;
Expr e = cast(t, x);
assert((e + e).type() == e.type());
}
// 3) 若一個型別為浮點數但另一個不是, 則浮點數參數將轉為浮點數
// (可能對數值大的整數造成精確度降低)
assert((u8 + f32).type() == Float(32));
assert((f32 + s64).type() == Float(32));
assert((u16 + f64).type() == Float(64));
assert((f64 + s32).type() == Float(64));
// 4) 若兩者為皆浮點數, 精準度較低的會被轉為較高的精準度
assert((f64 + f32).type() == Float(64));
// 上述原則用以處理所有浮點數的情況
// 以下三個原則用來處理整數的情況
// 5) 若參數其中之一為 C++ int, 而且另一個為 Halide::Expr
// C++ int 將強制轉為表示式型別(champ: 也就是 Expr).
assert((u32 + 3).type() == UInt(32));
assert((3 + s16).type() == Int(16));
// 若此原則造成整數 overflow, Halide 將會觸發一個錯誤
// 例如: 移除下行的註解將會以錯誤結束程式
// Expr bad = u8 + 257;
// 6) 若兩個型別皆為無號整數或皆為有號整數, 精準度較低的將轉為較高的型別
assert((u32 + u8).type() == UInt(32));
assert((s16 + s64).type() == Int(64));
// 7) If one type is signed and the other is unsigned, both
// arguments are promoted to a signed integer with the greater of
// the two bit widths.
// 7) 若一個型別為有號, 另一為無號,
// 兩者將會提升為精準度高於兩者的有號整數
assert((u8 + s32).type() == Int(32));
assert((u32 + s8).type() == Int(32));
// 必須注意, 對於相同精準度的無號整數可能潛在性造成
assert((u32 + s32).type() == Int(32));
// 當一個無號 Expr 轉換為精準度更高的有號型別, is converted to a wider signed type in
// 首先是先轉精準度更高的無號型別(zero-extended) 接著解釋為有號型別
// 例如, 將數值為 255 的 UInt(8) 轉為 Int(32) 後數值為 255 而非 -1
int32_t result32 = evaluate<int>(cast<uint32_t>(cast<uint8_t>(255)));
assert(result32 == 255);
// 當有號型別明確地以轉換操作轉為精準度更高的無號型別(對此提升規則不會自動處理),
// 會先轉為精準度更高的有號型別(sign-extended), 接著解釋為無號型別
// 例如: 轉換 數值為 -1 的 Int(8) 到 Init(16) 其值為 65535 而非 255
uint16_t result16 = evaluate<uint16_t>(cast<uint16_t>(cast<uint8_t>(-1)));
assert(result16 == 65535);
}
// Handle() 型別
{
// Handle 用來代表未知型別的 pointer,
// 對於任何 pointer 行別套用 type_of 都會回傳 Handle()
assert(type_of<void *>() == Handle());
assert(type_of<const char * const **>() == Handle());
// 無論編譯的目標平台為何, Handles 皆以 64-bit 存放
assert(Handle().bits() == 64);
// Expr 型別的 Handle 主要用在透過 Halide 傳遞到外部的程式碼
}
// 泛型程式碼
{
// 在 Halide 中明確使用型別主要用途在於參數的型別. 而在 C++ 中可能是透過模板.
// 在 Halide 中並不需要如此 - 能夠在 C++ 執行時期動態地檢查與修改型別
// 下列定義的函式會對兩個相同型別的表示作平均運算
Var x;
assert(average(cast<float>(x), 3.0f).type() == Float(32));
assert(average(x, 3).type() == Int(32));
assert(average(cast<uint8_t>(x), cast<uint8_t>(3)).type() == UInt(8));
}
Expr average(Expr a, Expr b) {
// 型別必須一致
assert(a.type() == b.type());
// 對於浮點數型別
if (a.type().is_float()) {
// 依照上方的規則3, 這裡的 '2' 將被提升為浮點數
return (a + b)/2;
}
// 對於整數型別, 必須計算更高精準度的中間值以避免 overflow
Type narrow = a.type();
Type wider = narrow.with_bits(narrow.bits() * 2);
a = cast(wider, a);
b = cast(wider, b);
return cast(narrow, (a + b)/2);
}
==
Lesson 15 - Generators (Part 1, Part 2)
#include "Halide.h"
#include <stdio.h>
using namespace Halide;
// Generators 是個更結構性方式來作 pipeline 的 ahead-of-time 編譯.
// 相較於在 Lesson 10 中是撰寫一個 int main() 與透過對等的指令介面,
// 這裡取而代之的是定義一個繼承自 Halide::Generator 的類別
class MyFirstGenerator : public Halide::Generator<MyFirstGenerator> {
public:
// 這裡以 Input 宣告 pipeline 的輸入作為公開的成員變數
// 其會以所宣告的次序出現在所產生的函數型別宣告中
Input<uint8_t> offset{"offset"};
Input<Buffer<uint8_t>> input{"input", 2};
// 亦以 Output 宣告 pipeline 的輸出作為公開的成員變數
Output<Buffer<uint8_t>> brighter{"brighter", 2};
// 通常也會在這個範圍以 Var 宣告變數, 如此可以被用在之後所增添的函式中
Var x, y;
// 接著定義建構並回傳 pipeline 的函式:
void generate() {
// 在 Lesson 10 當中, 在此是呼叫 Func::compile_to_file
// 而在 Generator 當中, 只需要去定義代表輸出 pipeline 的 Output
brighter(x, y) = input(x, y) + offset;
// 排程
brighter.vectorize(x, 16).parallel(y);
}
};
// 需搭配 tools/GenGen.cpp 來編譯此檔案.
// GenGen.cpp 定義了提供 CLI 來產生使用自訂 Generator 的 "int main(...)"
// 需要告知該程式關於自定義 generator 的存在
// 這是由下列這行達成:
HALIDE_REGISTER_GENERATOR(MyFirstGenerator, my_first_generator)
// 此外, 能夠同時在一個檔案放入多個 Generator
// 特別是在一同放入的多個 Generator 有個共用的程式碼.
// 以下為更複雜的例子:
class MySecondGenerator : public Halide::Generator<MySecondGenerator> {
public:
// 此 generator 也將會使用一些編譯時期參數
// 如此能夠編譯產生 pipeline 不同的衍生結果
// 這裡定義了是否告知要在排程中做平行化:
GeneratorParam<bool> parallel{"parallel", /* default value */ true};
// ... 而另一個表示將作為縮放係數的常數
GeneratorParam<float> scale{"scale",
1.0f /* default value */,
0.0f /* minimum value */,
100.0f /* maximum value */};
// 對所有基本純量型別都可定義 GeneratorParams
// 對於數值型別可以如同上面選擇性地提供最小與最大值
// 亦能夠對 enums 定義為 GeneratorParam
// 為了正確運作, 對此必須提供自字串到 enum 數值的對應
enum class Rotation { None, Clockwise, CounterClockwise };
GeneratorParam<Rotation> rotation{"rotation",
/* default value */
Rotation::None,
/* map from names to values */
{{ "none", Rotation::None },
{ "cw", Rotation::Clockwise },
{ "ccw", Rotation::CounterClockwise }}};
// 使用如同先前相同的 Inputs:
Input<uint8_t> offset{"offset"};
Input<Buffer<uint8_t>> input{"input", 2};
// 類似地以 Output 定義輸出. Note that we don't specify a type for the Buffer:
// 請注意在編譯時期並沒有指定型別,
// 而是必須明確地透過 "output.type" 這個 GeneratorParam 來指定
// (對此 Output 是被不明確地定義)
Output<Buffer<>> output{"output", 2};
// 另外如同先前以 Var 宣告了變數
Var x, y;
void generate() {
// 定義此 Func. 如同執行時期的 offset 參數般使用編譯時期的縮放係數
Func brighter;
brighter(x, y) = scale * (input(x, y) + offset);
// 取決於 enum 數值, 或許會做些旋轉的處
// 為了得到 GeneratorParam 的數值而將其轉型為是當的型別
// 該轉型在多數情況下潛在性地發生 (例如跟上面的縮放)
Func rotated;
switch ((Rotation)rotation) {
case Rotation::None:
rotated(x, y) = brighter(x, y);
break;
case Rotation::Clockwise:
rotated(x, y) = brighter(y, 100-x);
break;
case Rotation::CounterClockwise:
rotated(x, y) = brighter(100-y, x);
break;
}
// 之後將其轉型為所需要的輸出型別
output(x, y) = cast(output.type(), rotated(x, y));
// 最終 pipeline 的結構取決於 generator params.
// 排程亦然
// 在此對輸出做向量化, 由於不知道型別, 因此難以挑出好的係數.
// Generator 提供了一個稱為""natural_vector_size"輔助函式
// 這輔助函式將對於型別與編譯的目標平台選出合理的係數
output.vectorize(x, natural_vector_size(output.type()));
// 依參數決定平行化:
if (parallel) {
output.parallel(y);
}
// 若有旋轉, 以輸出的 scanline 方式依照型別作向量化與排程
if (rotation != Rotation::None) {
rotated
.compute_at(output, y)
.vectorize(x, natural_vector_size(rotated.output_types()[0]));
}
}
};
// 註冊第二個 generator:
HALIDE_REGISTER_GENERATOR(MySecondGenerator, my_second_generator)
// 在編譯此檔案後, 閱讀下方 lesson_15_generators_build.sh 內容以得知如何使用
(暫時不翻譯)
# To run this script:
# bash lesson_15_generators_usage.sh
# First we define a helper function that checks that a file exists
check_file_exists()
{
FILE=$1
if [ ! -f $FILE ]; then
echo $FILE not found
exit -1
fi
}
# And another helper function to check if a symbol exists in an object file
check_symbol()
{
FILE=$1
SYM=$2
if !(nm $FILE | grep $SYM > /dev/null); then
echo "$SYM not found in $FILE"
exit -1
fi
}
# Bail out on error
#set -e
# Set up LD_LIBRARY_PATH so that we can find libHalide.so
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../bin
export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:../bin
#########################
# Basic generator usage #
#########################
# First let's compile the first generator for the host system:
./lesson_15_generate -g my_first_generator -o . target=host
# That should create a pair of files in the current directory:
# "my_first_generator.a", and "my_first_generator.h", which define a
# function "my_first_generator" representing the compiled pipeline.
check_file_exists my_first_generator.a
check_file_exists my_first_generator.h
check_symbol my_first_generator.a my_first_generator
#####################
# Cross-compilation #
#####################
# We can also use a generator to compile object files for some other
# target. Let's cross-compile a windows 32-bit object file and header
# for the first generator:
./lesson_15_generate \
-g my_first_generator \
-f my_first_generator_win32 \
-o . \
target=x86-32-windows
# This generates a file called "my_first_generator_win32.lib" in the
# current directory, along with a matching header. The function
# defined is called "my_first_generator_win32".
check_file_exists my_first_generator_win32.lib
check_file_exists my_first_generator_win32.h
################################
# Generating pipeline variants #
################################
# The full set of command-line arguments to the generator binary are:
# -g generator_name : Selects which generator to run. If you only have
# one generator in your binary you can omit this.
# -o directory : Specifies which directory to create the outputs
# in. Usually a build directory.
# -f name : Specifies the name of the generated function. If you omit
# this, it defaults to the generator name.
# -n file_base_name : Specifies the basename of the generated file(s). If
# you omit this, it defaults to the name of the generated function.
# -e static_library,o,h,assembly,bitcode,stmt,html: A list of
# comma-separated values specifying outputs to create. The default is
# "static_library,h". "assembly" generates assembly equivalent to the
# generated object file. "bitcode" generates llvm bitcode for the pipeline.
# "stmt" generates human-readable pseudocode for the pipeline (similar to
# setting HL_DEBUG_CODEGEN). "html" generates an html version of the
# pseudocode, which can be much nicer to read than the raw .stmt file.
# -r file_base_name : Specifies that the generator should create a
# standalone file for just the runtime. For use when generating multiple
# pipelines from a single generator, to be linked together in one
# executable. See example below.
# -x .old=new,.old2=.new2,... : A comma-separated list of file extension
# pairs to substitute during file naming.
# target=... : The target to compile for.
# my_generator_param=value : The value of your generator params.
# Let's now generate some human-readable pseudocode for the first
# generator:
./lesson_15_generate -g my_first_generator -e stmt -o . target=host
check_file_exists my_first_generator.stmt
# The second generator has generator params, which can be specified on
# the command-line after the target. Let's compile a few different variants:
./lesson_15_generate -g my_second_generator -f my_second_generator_1 -o . \
target=host parallel=false scale=3.0 rotation=ccw output.type=uint16
./lesson_15_generate -g my_second_generator -f my_second_generator_2 -o . \
target=host scale=9.0 rotation=ccw output.type=float32
./lesson_15_generate -g my_second_generator -f my_second_generator_3 -o . \
target=host parallel=false output.type=float64
check_file_exists my_second_generator_1.a
check_file_exists my_second_generator_1.h
check_symbol my_second_generator_1.a my_second_generator_1
check_file_exists my_second_generator_2.a
check_file_exists my_second_generator_2.h
check_symbol my_second_generator_2.a my_second_generator_2
check_file_exists my_second_generator_3.a
check_file_exists my_second_generator_3.h
check_symbol my_second_generator_3.a my_second_generator_3
# Use of these generated object files and headers is exactly the same
# as in lesson 10.
######################
# The Halide runtime #
######################
# Each generated Halide object file contains a simple runtime that
# defines things like how to run a parallel for loop, how to launch a
# cuda program, etc. You can see this runtime in the generated object
# files.
echo "The halide runtime:"
nm my_second_generator_1.a | grep "[SWT] _\?halide_"
# Let's define some functions to check that the runtime exists in a file.
check_runtime()
{
if !(nm $1 | grep "[TSW] _\?halide_" > /dev/null); then
echo "Halide runtime not found in $1"
exit -1
fi
}
check_no_runtime()
{
if nm $1 | grep "[TSW] _\?halide_" > /dev/null; then
echo "Halide runtime found in $1"
exit -1
fi
}
# Declarations and documentation for these runtime functions are in
# HalideRuntime.h
# If you're compiling and linking multiple Halide pipelines, then the
# multiple copies of the runtime should combine into a single copy
# (via weak linkage). If you're compiling and linking for multiple
# different targets (e.g. avx and non-avx), then the runtimes might be
# different, and you can't control which copy of the runtime the
# linker selects.
# You can control this behavior explicitly by compiling your pipelines
# with the no_runtime target flag. Let's generate and link several
# different versions of the first pipeline for different x86 variants:
# (Note that we'll ask the generators to just give us object files ("-e o"),
# instead of static libraries, so that we can easily link them all into a
# single static library.)
./lesson_15_generate \
-g my_first_generator \
-f my_first_generator_basic \
-e o,h \
-o . \
target=host-x86-64-no_runtime
./lesson_15_generate \
-g my_first_generator \
-f my_first_generator_sse41 \
-e o,h \
-o . \
target=host-x86-64-sse41-no_runtime
./lesson_15_generate \
-g my_first_generator \
-f my_first_generator_avx \
-e o,h \
-o . \
target=host-x86-64-avx-no_runtime
# These files don't contain the runtime
check_no_runtime my_first_generator_basic.o
check_symbol my_first_generator_basic.o my_first_generator_basic
check_no_runtime my_first_generator_sse41.o
check_symbol my_first_generator_sse41.o my_first_generator_sse41
check_no_runtime my_first_generator_avx.o
check_symbol my_first_generator_avx.o my_first_generator_avx
# We can then use the generator to emit just the runtime:
./lesson_15_generate \
-r halide_runtime_x86 \
-e o,h \
-o . \
target=host-x86-64
check_runtime halide_runtime_x86.o
# Linking the standalone runtime with the three generated object files
# gives us three versions of the pipeline for varying levels of x86,
# combined with a single runtime that will work on nearly all x86
# processors.
ar q my_first_generator_multi.a \
my_first_generator_basic.o \
my_first_generator_sse41.o \
my_first_generator_avx.o \
halide_runtime_x86.o
check_runtime my_first_generator_multi.a
check_symbol my_first_generator_multi.a my_first_generator_basic
check_symbol my_first_generator_multi.a my_first_generator_sse41
check_symbol my_first_generator_multi.a my_first_generator_avx
echo "Success!"
沒有留言:
張貼留言