Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

インラインアセンブリ

概要

rustc におけるインラインアセンブリは、主に asm! マクロ呼び出しを受け取り、それを コンパイラのすべてのレイヤーを通して LLVM コード生成までつなぎ込むことを中心にしています。 さまざまな段階を通じて、 InlineAsm は一般に 3 つのコンポーネントで構成されます。

  • テンプレート文字列。これは InlineAsmTemplatePiece の配列として格納されます。 各要素はリテラル、またはオペランドのプレースホルダーのいずれかを表します (フォーマット文字列と同様です)。

    #![allow(unused)]
    fn main() {
    pub enum InlineAsmTemplatePiece {
        String(String),
        Placeholder { operand_idx: usize, modifier: Option<char>, span: Span },
    }
    }
  • asm! へのオペランドのリスト(in, [late]out, in[late]out, sym, const)。 これらは lowering の各段階で異なる形で表現されますが、 共通のパターンに従います。

    • in、out、inout はすべて、関連付けられたレジスタクラス(reg) または明示的なレジスタ("eax")を持ちます。
    • inout には 2 つの形式があります。 1 つは読み取りと書き込みの両方が行われる単一の式を持つ形式で、 もう 1 つは入力部分と出力部分に別々の 2 つの式を持つ形式です。
    • out と inout には late フラグ(lateout / inlateout)があり、この出力に対して レジスタアロケータが入力レジスタを再利用してよいことを示します。
    • out と inout の分割バリアントでは、出力に _ を指定できます。 これは出力が破棄されることを意味します。 これはアセンブリコード用のスクラッチレジスタを割り当てるために使用されます。
    • const は無名定数を参照し、 一般にはインライン const のように動作します。
    • sym はパス式のみを受け付けるため、少し特殊です。 そのパスは static または fn を指していなければなりません。
  • asm! マクロの末尾で設定されるオプション。 rustc にとって特に関心があるものは、 asm! が () ではなく ! を返すようにする NORETURN と、 フォーマット文字列の解析を無効にする RAW だけです。 残りのオプションは、ほとんど処理されずに LLVM にそのまま渡されます。

    #![allow(unused)]
    fn main() {
    bitflags::bitflags! {
        pub struct InlineAsmOptions: u16 {
            const PURE = 1 << 0;
            const NOMEM = 1 << 1;
            const READONLY = 1 << 2;
            const PRESERVES_FLAGS = 1 << 3;
            const NORETURN = 1 << 4;
            const NOSTACK = 1 << 5;
            const ATT_SYNTAX = 1 << 6;
            const RAW = 1 << 7;
            const MAY_UNWIND = 1 << 8;
        }
    }
    }

AST

InlineAsm は、AST では ast::InlineAsm 型 の式として表現されます。

asm! マクロは rustc_builtin_macros で実装されており、InlineAsm AST ノードを出力します。 テンプレート文字列は fmt_macros を使用して解析され、 位置指定オペランドと名前付きオペランドは明示的なオペランドインデックスに解決されます。 ターゲット情報はマクロ呼び出しでは利用できないため、 レジスタとレジスタクラスの検証は AST lowering まで延期されます。

HIR

InlineAsm は、HIR では hir::InlineAsm 型 の式として表現されます。

AST lowering では、InlineAsmRegOrRegClass が Symbol から実際のレジスタまたは レジスタクラスに変換されます。 テンプレート文字列のプレースホルダーに修飾子が指定されている場合、それらは そのオペランド型に対して許可されている集合に照らして検証されます。 最後に、入力と出力の明示的なレジスタについて、 競合(同じレジスタが異なるオペランドに使用されていること)がないかチェックされます。

型検査

各レジスタクラスには、それとともに使用できる型の許可リストがあります。 すべてのオペランドの型が決定された後、 intrinsicck パスはこれらの型が許可リストに含まれていることをチェックします。 また、分割された inout オペランドの型に互換性があること、および const オペランドが整数または浮動小数点数であることもチェックします。 渡された型に基づいて、オペランドにテンプレート修飾子を 使用すべき場合には、必要に応じて提案が出力されます。

THIR

InlineAsm は、THIR では InlineAsmExpr 型 の式として表現されます。

HIR と比較した唯一の重要な変更は、Sym が、expr が fn の Literal ZST である SymFn、 または static の DefId を指す SymStatic のいずれかに lowering されていることです。

MIR

InlineAsm は、MIR では TerminatorKind::InlineAsm バリアント の Terminator として表現されます。

THIR lowering の一部として、InOut および SplitInOut オペランドは、 個別の in_value と out_place を持つ分割形式に lowering されます。

意味的には、InlineAsm ターミネータは Call ターミネータに似ていますが、 Call が単一の戻り場所出力しか持たないのに対して、複数の出力場所を持つ点が異なります。

コード生成

オペランドは、LLVM コード生成に渡される前にもう一度 lowering されます。 これは rustc_codegen_ssa の InlineAsmOperandRef 型 によって表現されます。

オペランドは、次のように LLVM オペランドおよび制約コードに lowering されます。

  • out および inout オペランドの出力部分は、LLVM の要求どおり最初に追加されます。 late 出力オペランドには制約コードの先頭に = が追加され、 非 late 出力オペランドには制約コードの先頭に =& が追加されます。
  • in オペランドは通常どおり追加されます。
  • inout オペランドは対応する出力オペランドに結び付けられます。
  • sym オペランドは、"s" 制約を使用して、 関数ポインタまたはポインタとして渡されます。
  • const オペランドは文字列にフォーマットされ、テンプレート文字列に直接挿入されます。

テンプレート文字列は LLVM 形式に変換されます。

  • $ 文字は $$ としてエスケープされます。
  • const オペランドは文字列に変換され、直接挿入されます。
  • プレースホルダーは ${X:M} としてフォーマットされます。 ここで、X はオペランドインデックス、M は修飾子文字です。 修飾子は Rust 形式から LLVM 形式に変換されます。

さまざまなオプションは、clobber 制約または LLVM 属性に変換されます。 詳細については RFC を参照してください。

LLVM は特定の制約コードに対して受け付ける型について、かなり厳しい場合があることに注意してください。 そのため、サポートされている型との間で変換を挿入する必要があることがあります。 各レジスタクラスでサポートされる型の詳細については、 LLVM のターゲット固有の ISelLowering.cpp ファイルを参照してください。

新しいアーキテクチャのサポート追加

アーキテクチャにインラインアセンブリのサポートを追加することは、ほとんどの場合、そのアーキテクチャのレジスタと レジスタクラスを定義することです。 レジスタクラスのすべての定義は、 compiler/rustc_target/asm/ にあります。

さらに、これらのレジスタクラスを LLVM 制約コードへ lowering する処理を compiler/rustc_codegen_llvm/asm.rs に実装する必要があります。 新しいアーキテクチャを追加する場合は、必ず LLVM のソースコードと相互参照してください。

  • LLVM には、特定の制約コードで使用できる型に制限があります。 lib/Target/${ARCH}/${ARCH}ISelLowering.cpp の getRegForInlineAsmConstraint 関数を参照してください。
  • LLVM は、内部使用のために特定のレジスターを予約しており、 そのためインラインアセンブリブロックの前後でそれらが適切に保存/復元されません。 これらのレジスターは、lib/Target/${ARCH}/${ARCH}RegisterInfo.cpp の getReservedRegs 関数に一覧されています。 フレーム/ベースポインターのような「条件付きで」予約されるレジスターは、 Rust の目的では常に予約済みとして扱わなければなりません。これは、 関数がフレーム/ベースポインターを必要とするかどうかを事前に知ることができないためです。

テスト

インラインアセンブリ用の各種テストが利用可能です。

  • tests/assembly-llvm/asm
  • tests/ui/asm
  • tests/codegen-llvm/asm-*

インラインアセンブリでサポートされるすべてのアーキテクチャには、 tests/assembly-llvm/asm に、レジスタークラスと型のすべての組み合わせをテストする網羅的なテストがなければなりません。