Vengineerの妄想(準備期間)

人生は短いけど、長いです。人生を楽しみましょう!

Tiramisu、その4


Tiramisuは、対象としている関数を Halideと同様に オブジェクトファイルに生成します。
その生成したオブジェクトファイルをリンクして使います。

では、どんな感じでオブジェクトファイルを生成しているのでしょうか?

テストプログラム(tests/test_01.cpp)の後半分を以下に示します。
    function0.set_arguments({&buf0});
    function0.gen_time_space_domain();

    // gen_isl_astにて、ISLのAST(Abstruct Syntax Tree)を生成します
    function0.gen_isl_ast();

    // ISLのASTからHalideのStatementに変換します
    function0.gen_halide_stmt();

    // HalideのStatementからオブジェクトファイルを生成します
    function0.gen_halide_obj("build/generated_fct_test_01.o");

gen_halide_objでは、
    // ここで、Lowering して
    Halide::Module m = lower_halide_pipeline(this->get_name(), target, fct_arguments,
                                             Halide::Internal::LoweredFunc::External,
                                             this->get_halide_stmt());

    // オブジェクトファイルを生成
    m.compile(Halide::Outputs().object(obj_file_name));

    // ヘッダファイルを生成
    m.compile(Halide::Outputs().c_header(obj_file_name + ".h"));


lower_halide_pipelineでは、以下のような Lowering を行っています。
デバッグ文等およびGPU関連コードは、削除しています。
    Module result_module(pipeline_name, t);

    map<string, Function> env;

    s = sliding_window(s, env);

    s = remove_undef(s);

    s = uniquify_variable_names(s);

    s = storage_folding(s, env);

    s = simplify(s, false);

    s = skip_stages(s, order);
    s = unpack_buffers(s);

    s = simplify(s);
    s = unify_duplicate_lets(s);
    s = remove_trivial_for_loops(s);

    s = unroll_loops(s);
    s = simplify(s);

    s = vectorize_loops(s, t);
    s = simplify(s);

    s = rewrite_interleavings(s);
    s = simplify(s);

    s = partition_loops(s);
    s = simplify(s);
 
    s = trim_no_ops(s);
 
    s = inject_early_frees(s);
 
    s = common_subexpression_elimination(s);

    s = remove_dead_allocations(s);
    s = remove_trivial_for_loops(s);
    s = simplify(s);

    s = inject_hexagon_rpc(s, t, result_module);

    vector<Argument> public_args = args;

    class StrengthenRefs : public IRMutator {
        using IRMutator::visit;
        void visit(const Call *c) {
            IRMutator::visit(c);
            c = expr.as<Call>();
            //internal_assert(c);
            if (c->func.defined()) {
                FunctionPtr ptr = c->func;
                ptr.strengthen();
                expr = Call::make(c->type, c->name, c->args, c->call_type,
                                  ptr, c->value_index,
                                  c->image, c->param);
            }
        }
    };
    s = StrengthenRefs().mutate(s);

ここまでで、Lowering は終了です。
LoweredFunc にて、関数に変換。。。LoweredFunc は、Halide内で定義。
    LoweredFunc main_func(pipeline_name, public_args, s, linkage_type);

    result_module.append(main_func);

    if (!t.has_feature(Target::JIT)) {
        add_legacy_wrapper(result_module, main_func);
    }

    wrap_legacy_extern_stages(result_module);

    return result_module;

Tiramisu は、Halideのコード生成部(LLVMでのコード生成)は利用していますが、
Lowering に関しては、Halideが提供しているものを使って独自に行っています。

Tiramisu: A Code Optimization Framework for High Performance Systemsの7.1 Halide to Tiramisuの以下の部分
For CPU code generation, we convert Layer IV Tiramisu IR into low-level transformed Halide IR 
(bypassing all lowering in the original Halide compiler) and feed it into the Halide LLVM code generator. 

CPUコード生成では、Layer IVのTiramisu IRをローレベルに変換したHalide IRにコンバートする。
この時、オリジナルのHalideコンパイラのすべてのLoweringをバイバスします。
生成したHalide IRは、Halideのコード生成に供給します。
と。。。