인라인 어셈블리
개요
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가지 형태가 있습니다. 하나는 읽기와 쓰기가 모두 이루어지는 단일 표현식을 사용하는 형태이고, 다른 하나는 입력 부분과 출력 부분에 각각 별도의 표현식을 사용하는 형태입니다.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 로워링 단계로 미뤄집니다.
HIR
InlineAsm은 HIR에서 hir::InlineAsm 타입을 가진 표현식으로 표현됩니다.
AST 로워링 단계에서 InlineAsmRegOrRegClass가 Symbol에서 실제 레지스터 또는 레지스터 클래스로 변환됩니다. 템플릿 문자열 자리 표시자에 수정자가 지정된 경우, 해당 피연산자 타입에 허용되는 수정자 집합에 맞는지 검증합니다. 마지막으로, 입력과 출력에 사용된 명시적 레지스터가 서로 충돌하는지(서로 다른 피연산자에 같은 레지스터를 사용하는지) 검사합니다.
타입 검사
각 레지스터 클래스에는 함께 사용할 수 있는 타입의 허용 목록이 있습니다. 모든 피연산자의 타입이 결정되면 intrinsicck 패스가 이 타입들이 허용 목록에 있는지 검사합니다. 또한 분할된 inout 피연산자의 타입이 서로 호환되는지, const 피연산자가 정수나 부동소수점 수인지도 검사합니다. 피연산자에 전달된 타입으로 볼 때 그 피연산자에 템플릿 수정자를 사용해야 하는 경우, 필요한 곳에 제안을 출력합니다.
THIR
InlineAsm은 THIR에서 InlineAsmExpr 타입의 표현식으로 표현됩니다.
HIR과 비교했을 때 유일하게 중요한 변경점은 Sym이 expr이 fn의 Literal ZST인 SymFn, 또는 static의 DefId를 가리키는 SymStatic 중 하나로 낮춰졌다는 점입니다.
MIR
InlineAsm은 MIR에서 TerminatorKind::InlineAsm 배리언트를 사용하는 Terminator로 표현됩니다
THIR 로워링 과정에서 InOut 및 SplitInOut 피연산자는 in_value와 out_place가 분리된 분할 형태로 로워링됩니다.
의미론적으로 InlineAsm 종결자는 Call 종결자와 비슷하지만, Call의 출력은 반환 위치 하나뿐인 반면 InlineAsm에는 출력 위치가 여러 개 있다는 점이 다릅니다.
코드 생성
피연산자는 LLVM 코드 생성에 전달되기 전에 한 번 더 낮춰집니다. 이는 rustc_codegen_ssa의 InlineAsmOperandRef 타입으로 표현됩니다.
피연산자는 다음과 같이 LLVM 피연산자와 제약 코드로 낮춰집니다.
- LLVM의 요구에 따라
out피연산자와inout피연산자의 출력 부분이 가장 먼저 추가됩니다. 늦은(late) 출력 피연산자는 제약 코드에=접두사가 붙고, 늦지 않은 출력 피연산자는 제약 코드에=&접두사가 붙습니다. in피연산자는 일반적인 방식으로 추가됩니다.inout피연산자는 짝이 되는 출력 피연산자에 묶입니다.sym피연산자는"s"제약을 사용하여 함수 포인터나 포인터로 전달됩니다.const피연산자는 문자열로 포맷되어 템플릿 문자열에 바로 삽입됩니다.
템플릿 문자열은 다음과 같이 LLVM 형식으로 변환됩니다.
$문자는$$로 이스케이프됩니다.const피연산자는 문자열로 변환되어 바로 삽입됩니다.- 플레이스홀더는
${X:M}형식으로 포맷되며, 여기서X는 피연산자 인덱스이고M은 수정자 문자입니다. 수정자는 Rust 형식에서 LLVM 형식으로 변환됩니다.
여러 옵션은 클로버 제약이나 LLVM 속성으로 변환됩니다. 자세한 내용은 RFC를 참고하십시오.
LLVM은 특정 제약 코드에 어떤 타입을 허용하는지에 대해 꽤 까다로울 때가 있으므로, 지원되는 타입으로 변환하거나 지원되는 타입에서 변환하는 코드를 삽입해야 할 때가 있다는 점에 유의하십시오. 레지스터 클래스마다 어떤 타입이 지원되는지 자세히 알아보려면 LLVM의 타깃별 ISelLowering.cpp 파일을 참고하십시오.
새 아키텍처 지원 추가하기
아키텍처에 인라인 어셈블리 지원을 추가하는 작업은 대부분 그 아키텍처의 레지스터와 레지스터 클래스를 정의하는 일입니다. 레지스터 클래스의 정의는 모두 compiler/rustc_target/asm/에 있습니다.
또한 compiler/rustc_codegen_llvm/asm.rs에서 이러한 레지스터 클래스를 LLVM 제약 코드로 로워링하는 기능도 구현해야 합니다.
새 아키텍처를 추가할 때는 반드시 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에 레지스터 클래스와 타입의 모든 조합을 검사하는 포괄적인 테스트를 갖추어야 합니다.