Если вам нужно работать на самом низком уровне или повысить производительность
программы, то у вас может возникнуть необходимость управлять процессором
напрямую. Rust поддерживает использование встроенного ассемблера и делает это с
помощью с помощью макроса asm!
. Синтаксис примерно соответствует синтаксису
GCC и Clang:
asm!(assembly template : output operands : input operands : clobbers : options );
Использование asm
является закрытой возможностью (требуется указать
#![feature(asm)]
для контейнера, чтобы разрешить ее использование) и, конечно
же, требует unsafe
блока.
Примечание: здесь примеры приведены для x86/x86-64 ассемблера, но поддерживаются все платформы.
Шаблон инструкции ассемблера (assembly template) является единственным
обязательным параметром, и он должен быть представлен строкой символов (т.е.
""
)
#![feature(asm)] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn foo() { unsafe { asm!("NOP"); } } // other platforms #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] fn foo() { /* ... */ } fn main() { // ... foo(); // ... }
(Далее атрибуты feature(asm)
и #[cfg]
будут опущены.)
Выходные операнды (output operands), входные операнды (input operands),
затираемое (clobbers) и опции (options) не являются обязательными, но вы должны
будете добавить соответствующее количество :
если хотите пропустить их:
asm!("xor %eax, %eax" : : : "{eax}" );
Пробелы и отступы также не имеют значения:
#![feature(asm)] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn main() { unsafe { asm!("xor %eax, %eax" ::: "{eax}"); } }asm!("xor %eax, %eax" ::: "{eax}");
Входные и выходные операнды имеют одинаковый формат:
:"ограничение1"(выражение1), "ограничение2"(выражение2), ..."
. Выражения для
выходных операндов должны быть либо изменяемыми, либо неизменяемыми, но еще не
иницилиализированными, L-значениями:
fn add(a: i32, b: i32) -> i32 { let c: i32; unsafe { asm!("add $2, $0" : "=r"(c) : "0"(a), "r"(b) ); } c } fn main() { assert_eq!(add(3, 14159), 14162) }
Однако, если вы захотите использовать реальные операнды (регистры) в этой
позиции, то вам потребуется заключить используемый регистр в фигурные скобки
{}
, и вы должны будете указать конкретный размер операнда. Это полезно для
очень низкоуровневого программирования, когда важны регистры, которые вы
используете:
let result: u8; asm!("in %dx, %al" : "={al}"(result) : "{dx}"(port)); result
Некоторые инструкции могут изменять значения регистров, поэтому мы используем список затираемого. Он указывает компилятору, что тот не должен допускать какого-либо изменение значений этих регистров, чтобы они оставались корректными.
#![feature(asm)] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn main() { unsafe { // Put the value 0x200 in eax asm!("mov $$0x200, %eax" : /* no outputs */ : /* no inputs */ : "{eax}"); } }// Put the value 0x200 in eax asm!("mov $$0x200, %eax" : /* no outputs */ : /* no inputs */ : "{eax}");
Если входные и выходные регистры уже заданы в ограничениях, то их не нужно перечислять здесь. В противном случае, любые другие регистры, используемые явно или неявно, должны быть перечислены.
Если ассемблер изменяет регистр кода условия cc
, то он должен быть указан в
качестве одного из затираемых. Точно так же, если ассемблер модифицирует память,
то должно быть указано memory
.
Последний раздел, options
, специфичен для Rust. Формат представляет собой
разделенные запятыми текстовые строки (т.е. :"foo", "bar", "baz"
). Он
используется для того, чтобы задать некоторые дополнительные данные для
встроенного ассемблера:
На текущий момент разрешены следующие опции:
volatile — эта опция аналогична __asm__ __volatile__ (...)
в gcc/clang;
alignstack — некоторые инструкции ожидают, что стек был выровнен определенным образом (т.е. SSE), и эта опция указывает компилятору вставить свой обычный код выравнивания стека;
intel — эта опция указывает использовать синтаксис Intel вместо используемого по умолчанию синтаксиса AT&T.
let result: i32; unsafe { asm!("mov eax, 2" : "={eax}"(result) : : : "intel") } println!("eax is currently {}", result);
Текущая реализация макроса asm!
--- это прямое связывание с
встроенным ассемблером LLVM, поэтому изучите и их
документацию, чтобы лучше понять список затираемого, ограничения и
др.