#汇编 #内联 #变量 #糖语法 #语法 #

rusty-asm

在 Rust 和内联汇编之间的语法糖层

4 个版本

0.2.1 2018年11月7日
0.2.0 2018年11月7日
0.1.1 2018年11月1日
0.1.0 2018年11月1日

开发工具 中排名 #1103

每月下载量 31

MIT/Apache 协议

57KB
625 代码行

Build Status Coverage Status

rusty-asm

在 Rust 和内联汇编之间的语法糖层

Rust 目前提供了一个 asm! 宏,用于在 Rust 中定义的函数内编写内联汇编。它使用与 GCC 内联汇编相同的基本格式——但这种格式并不是最直观的。以下是一个小示例,来自 OSDev wiki,已转换为 Rust 语法

// Retrieves a value from memory in a different segment than the one currently being used (x86[-64])
unsafe fn farpeekl(segment_selector: u16, offset: *const u32) -> u32 {
    let ret: u32;
    asm!("
        push %fs
        mov $1, %fs
        mov %fs:($2), $0
        pop %fs
    "   : "=r"(ret) : "r"(segment_selector), "r"(offset)
    );
    ret
}

(这个例子在我个人看来比用 GCC 编写时看起来更简洁,但仍然需要改进。)

asm! 宏目前是一个不稳定的功能,仅在 nightly 版本中可用。据我所知,原因有几个,但其中之一是语法。很容易忘记事物的精确顺序(先输入还是先输出?),并且其中一些部分是多余的。使用 "=r""~r" 表示寄存器分别是输出、输入或被覆盖,但不同类型之间也必须用冒号分隔。因此,使用 asm!,程序员需要记住两种方式来告诉编译器对每个寄存器的期望。

此 crate 尝试改进内联汇编周围的语法,使其更易于阅读和编写,无需每次都查找所需的格式。它通过使用过程宏(1)来重载 Rust 的 let 语句,使变量能够存储有关它们在后续内联汇编块中将如何使用的相关信息,并通过(2)解析 asm 块来允许使用新语法定义的变量直接在内联汇编代码中使用。

变更日志

  • 0.1 - 初次发布
  • 0.2 - 现在支持内部块。

设置

要使用此 crate,请在 Cargo.toml 中添加以下内容

[dependencies]
rusty-asm = "0.2.1"

然后在主源文件中引用此 crate 并激活所需的特性

extern crate rusty_asm;
use rusty_asm::rusty_asm; // Because who wants to write `rusty_asm::rusty_asm!`?

请注意,您仍然需要一个夜间编译器来使用它。 rusty_asm 并不能使内联汇编稳定。

支持的功能

以下功能可用

  • proc-macro:使 proc-macro2 作为 proc_macro 的薄包装,包括仍不稳定的部分。这个特性的好处是它允许 rusty-asm 提供自己的警告,这应该会让调试您的代码更容易。

基本语法

在您想添加内联汇编的位置,调用 rusty_asm! 如此

rusty_asm! {
    // (arbitrary Rust statements go here)

    asm (/* maybe some options in here */) {
        // (insert your ASM code here, in quotes)
    }

    // (possibly some cleanup code here)
}

asm 块的内容需要是一个字符串字面量,以确保 Rust 的解析器不会破坏格式。 (宏目前无法访问空白信息。) 有关更详细的信息,请参阅下面的示例。

此外,同一个 rusty_asm! 块中可以有多个 asm 块,以防您想重复使用您的桥接变量(见下文)。

桥接变量

一个 桥接变量 是通过在其定义中包含输入/输出/破坏信息来连接 Rust 和汇编之间的差距的变量。它们只能在 rusty_asm! 块内部定义,由于宏创建了一个新的作用域,因此当执行离开这些块时(以及在该作用域中定义的任何其他变量)它们会被丢弃。为了定义一个桥接变量,您需要使用以下三个仅在 rusty_asm! 块内部有效的关键词之一

  • in
  • out
  • inout

每个这些关键词都用于 "let" 语句来定义一个桥接变量。确切的语法如下

let [mut] <identifier>: [<type>:] in(<constraint>) [= <expression>];
let [mut] <identifier>: [<type>:] out(<constraint>) [= <expression>];
let [mut] <identifier>: [<type>:] inout(<constraint>) [= <expression>];

可选的 <type> 是任何 Rust 类型,就宏所知,但它应该是可以放入适当寄存器的某种类型(例如,对于通用整数寄存器,可以是 usizei8 等)。

此外,您可以使用以下语法指定您将破坏特定的寄存器(或破坏内存)

clobber(<constraint>);

其中 <constraint> 是寄存器的名称(例如,"eax")或 "memory"

这些语句与以下方式对应的 LLVM 约束

// in, out, or inout:
<new-constraint>(<identifier>)
// clobber
<new-constraint>

在每种情况下,<new_constraint> 等同于 <constraint>,除了对于 outclobber 关键字,需要在 '=''~' 前面添加以满足 asm! 和 LLVM。因此,例如,如果你将约束写为 "r",它将被自动转换为 "=r""~r",然后才交给编译器。关键字 inout 将产生两个新的约束:(1)与关键字 out 等效的约束(例如 "=r")和(2)与之相关的输入约束(例如 "0")。

为了让 Rust 了解如何处理桥接变量,rusty_asm! 在宏展开过程中删除了新的关键字和约束,因此对于 Rust 来说,它们只是普通变量。

asm 块

当遇到 asm 块时,它将被直接转换为 asm! 调用,使用迄今为止创建的所有约束。asm 块的语法如下:

asm [(<options>)] {
    "<asm-code>"
}

<options> 是一个可选的逗号分隔的选项列表,如果在 asm! 后面使用,这些选项将出现在第 4 个冒号之后,例如 "volatile"<asm-code> 是纯汇编代码,用引号括起来,除了它可以使用在 asm 块上方定义的桥接变量。

为了在 asm 块内部引用桥接变量,在代码中插入 $<ident>,其中 <ident> 是变量的标识符。与 asm! 宏一样,$$ 编码一个字面美元符号。

rusty_asm! 块和作用域

新的宏将整个内容放入一个新的作用域中,因此其中定义的任何变量在结束时都会被丢弃。如果需要保留这些值,可以在宏结束时使用常规 Rust 代码将它们的值移动到宏作用域外的变量中。此外,就像 Rust 的任何代码块一样,这个代码块也有一个返回值,可以在块末尾使用表达式来使用它。

此外,从版本 0.2 开始,该宏也正确处理了内部块、遮蔽和丢弃桥接变量,就像 Rust 遮蔽和丢弃常规变量一样。这意味着你现在可以编写如下代码:

// Sends 1, 2, or 4 bytes at once to an ISA address (x86/x64).
unsafe fn poke_isa(port: u16, value: usize, bytes: u8) {
    rusty_asm! {
        let port: in("{dx}") = port;
        if bytes == 1 {
            let value: in("{al}") = value as u8;
            asm("volatile", "intel") {
                "out $port, $value"
            }
        } else if bytes == 2 {
            let value: in("{ax}") = value as u16;
            asm("volatile", "intel") {
                "out $port, $value"
            }
        } else {
            assert_eq!(bytes, 4);
            let value: in("{eax}") = value as u32;
            asm("volatile", "intel") {
                "out $port, $value"
            }
        }
    };
}

if letwhile let 结构中定义桥接变量尚未得到支持,因为 Rust 也不支持在这些结构中进行显式类型注解,我想这样的语法会变得过于复杂。

进一步阅读

有太多平台特定的约束和选项,无法在此全部列出。请点击以下链接获取更多信息。

使用示例

请注意,虽然所有这些示例都使用了 x86 汇编,但 rusty_asm! 应该可以与 Rust 支持的任何汇编方言一起工作(这很可能意味着任何 LLVM 支持的方言)。

// Disables interrupts on an x86 CPU.
unsafe fn disable_interrupts() {
    rusty_asm! {
        asm("volatile") { // This block has to be marked "volatile" to make sure the compiler, seeing
           "cli"          // no outputs and no clobbers, doesn't assume it does nothing and
        }                 // decide to "optimize" it away.
    };
}
// Shifts the hexadecimal digits of `existing` up and puts `digit` in the resulting gap.
fn append_hex_digit(existing: usize, digit: u8) -> usize {
    assert!(digit < 0x10);
    unsafe {
        rusty_asm! {
            let mut big: inout("r") = existing;
            let little: in("r") = digit as usize;

            asm {"
                shll %4, $big
                orl $little, $big
            "}

            big
        }
    }
}

assert_eq!(append_hex_digit(0, 0), 0);
assert_eq!(append_hex_digit(0, 0xf), 0xf);
assert_eq!(append_hex_digit(4, 2), 0x42);

限制

桥接变量声明语法比一般的 let 语句的语法稍微严格一些,因为它只允许在 let 关键字之后使用一个标识符,而不是任意模式。因此,例如,这个语句将不会工作

rusty_asm! {
    let (a, b): (in("r"), out("r")) = (12, 14);
    /* ... */
}

依赖

~2MB
~46K SLoC