オフィスアワーがそろそろ始まるよ!()

アセンブリ

ベアメタルで動作するプログラムを開発する場合や高度な最適化を行う際、機械語を直接書きたいことがあります。Zenではそのような場合に備えて、インラインアセンブリの機能を提供しています。

インラインアセンブリの構文はLLVMのインラインアセンブリGCCのインラインアセンブリと似ています。

インラインアセンブリの基本構文は、次の通りです。

    asm (
        "アセンブリテンプレート"
        : [出力オペランド] "出力制約" (->戻り値型)
        : [入力オペランドのリスト] "入力制約" (変数名), ...
        : 破壊されるレジスタ
    );

インラインアセンブリは式として評価されます。そのため、値を得ることができます。

    const x = asm( ... );

インラインアセンブリが副作用を持つ場合、コンパイラの最適化によりインラインアセンブリが削除されないようにvolatileキーワードを付けます。asm式が戻り値を持たない場合、volatileを付与しないとコンパイルエラーになります。

    asm volatile ( ... );

アセンブリテンプレート

アセンブリテンプレートは必須の引数です。これは文字列リテラルで与えます。最も単純なインラインアセンブリは次のようになります。

fn halt() void {
    asm volatile ("hlt");
}

このコードをコンパイルすると、次のように機械語命令のhltがそのまま出力されます。

halt:
        hlt
        ret

プロセッサアーキテクチャごとに異なるアセンブリを出力する場合、次のように書くことができます。

const builtin = @import("builtin");
fn halt() void {
    switch (builtin.arch) {
        .x86_64 => asm volatile ("hlt"),
        .thumb => asm volatile ("wfi"),
        else => @compileError("not supported"),
    }
}

オペランド

オペランド指定の包括的な説明は、本書では省略しますが、利用例をお見せします。x86_64のアセンブリについては、読者があらかじめご存知の前提で説明します。

fn move() void {
    const a: u64 = 200;
    const result = asm volatile (
        "mov %[arg], %[output]"
        :[output] "={rbx}" (->u64)
        :[arg] "{rcx}" (a)
    );
}

"mov %[arg], %[output]"のアセンブリテンプレート内で、%[オペランド名]とすることで、[オペランド名]で後から参照することができます (明示的にオペランド名を付けない場合、$0$1といったふうに後に続くオペランドを順番に指定することもできます) 。

[output] "={rbx}" (->u64)は出力オペランドです。これで、結果をrbxレジスタに格納し、その値を戻り値としてu64型で返します。

[arg] "{rcx}" (a)は入力オペランドです。これで、const a: u64 = 200;の変数arcxレジスタに割り当て、入力オペランドとして利用することができます。

破壊されるレジスタ

アセンブリ命令を実行した結果、レジスタの値を変更する可能性があります。コンパイラがレジスタの値が変更されたことに気づかず、そのレジスタを使ってしまわないように、破壊されるレジスタを指定します。入力オペランドと出力オペランドは変更される可能性があり、これはコンパイラに伝わっています。それ以外で値が変更される可能性のあるレジスタを指定して下さい。

複数行にわたるアセンブリ

複数行にわたるアセンブリを記述することもできます。

    asm volatile(
        \\ mov $1, %%rax
        \\ add %%rax, %%rax
    );

グローバルアセンブリ

トップレベルのcomptimeブロック内にアセンブリを書くことができます。これはグローバルアセンブリと呼ばれます。

comptime {
    asm (
        \\.global double;
        \\.type double, @function;
        \\double:
        \\  add %rax, %rax
        \\  retq
    );
}

extern fn double(a: u64) u64;

最初にアセンブリが必要なブートローダを書く場合などに便利です。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.