2018年2月19日 星期一

Halide Tutorial 非官方中文翻譯 - Part 6

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

==

Lesson 13 - Tuples

// 至今 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");
    }
}

==

lesson 14: The Halide type system


// 所有的 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!"


沒有留言:

在 ARM 平台上使用 Function Multi-Versioning (FMV) - 以使用 Android NDK 為例

Function Multi-Versioning (FMV) 過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過...