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