4 个版本
0.1.3 | 2023 年 1 月 16 日 |
---|---|
0.1.2 | 2023 年 1 月 16 日 |
0.1.1 | 2023 年 1 月 15 日 |
0.1.0 | 2023 年 1 月 15 日 |
#828 在 Rust 模式
每月 21 次下载
25KB
210 行
汇编块
这个包提供了一个宏,可以将标记转换为字符串,主要用于 core::arch::asm!
宏。
添加
cargo add asm_block
示例
use asm_block::asm_block;
macro_rules! f {
($a: tt, $b: tt, $c: tt, $d: tt, $k: tt, $s: literal, $t: literal, $tmp: tt) => {
asm_block! {
mov $tmp, $c;
add $a, $k;
xor $tmp, $d;
and $tmp, $b;
xor $tmp, $d;
lea $a, [$a + $tmp + $t];
rol $a, $s;
add $a, $b;
}
};
}
asm!(
f!(eax, ebx, ecx, edx, [ebp + 4], 7, 0xd76aa478, esi),
f!({a}, {b}, {c}, {d}, {x0}, 7, 0xd76aa478, {t}),
f!({a:e}, {b:e}, {c:e}, {d:e}, [{x} + 4], 7, 0xd76aa478, {t:e}),
a = out(reg) _,
b = out(reg) _,
c = out(reg) _,
d = out(reg) _,
x0 = out(reg) _,
x = out(reg) _,
t = out(reg) _,
);
设计
asm_block
遵循非常简单的规则,并且主要依赖于底层汇编器的空白容错。
转换规则
- 将
;
转换为\n
。 - 将
;
转换为\n
。 @
和:
前后没有空格。.<ident>
后必须有一个空格。- 不违反上述规则,
.
前没有空格。 - 将大括号
{
和}
之间的所有内容连接起来,不添加任何空格。 - 将所有其他标记原样转录(通过
stringify!
),并在之后添加一个空格。
这应该适用于大多数汇编代码。我们已检查在 $
、#
、!
、%
、:
、=
后添加空格不会使使用 x86_64
目标和 aarch64
目标的汇编代码无效。
动机
考虑以下使用 x86_64
汇编的代码
unsafe fn f() -> u64 {
let mut x = 20;
asm!(
".macro mad x, y",
" mul x, y",
" lea x, [x + y]",
".endm",
"mad {x}, 5",
x = inout(reg) x
);
x
}
如果我们想在另一个函数中重用 mad
,我们必须复制宏的原文并更改其名称。否则,我们将遇到由于名称冲突导致的编译错误。
unsafe fn f() -> u64 {
let mut x = 20;
asm!(
".macro mad x, y",
" mul x, y",
" lea x, [x + y]",
".endm",
"mad {x}, 5",
x = inout(reg) x
);
x
}
unsafe fn g() -> u64 {
let mut x = 10;
asm!(
// Only compiles if we remove this macro definition
// or rename it to another name
".macro mad x, y",
" mul x, y",
" lea x, [x + y]",
".endm",
"mad {x}, 8",
x = inout(reg) x
);
x
}
上述代码将失败
error: macro 'mad' is already defined
如果我们省略在 g
(()) 中 mad
的定义,它将可以编译,但仅当 g
(()) 在 f
(()) 之后执行。不清楚哪个函数应该包含这个定义,因此唯一合理的选项是将它放在 global_asm!
代码中。但再次强调,很难保证定义的输出在真正使用之前。
在这种情况下,使用 Rust 宏是自然的,但由于 asm!
接受模板字符串,所以替换元变量变得繁琐。
macro_rules! mad {
($x: ident, $y: literal) => {
concat!(
"mul {", stringify!($x), "}, ", stringify!($y), "\n",
"lea {", stringify!($x), "}, [{", stringify!($x), "}+", stringify!($y), "]"
)
};
}
unsafe fn f() -> u64 {
let mut x = 20;
asm!(
mad!(x, 5),
x = inout(reg) x
);
x
}
这种方法存在一些缺点
- 定义非常杂乱,使得阅读和理解变得困难。如果定义变长,情况会更糟,如果
rustfmt
尝试格式化它,那么就会更加糟糕。 - 当定义变长时,很容易忘记
,
和\n
。 mad!
只能接受一个命名的寄存器作为第一个参数和一个字面量作为第二个参数。我们不能调用mad!(x, rbx)
或mad!([rax], rbp)
,如果我们使用汇编宏,我们原本可以这样做。尝试通过将ident
和literal
改为tt
来解决这个问题也是有问题,因为stringify!({x})
变为"{ x }"
,这是一个无效的占位符。
这个包通过提供一个更容易组合汇编代码的宏来解决此问题。
use asm_block::asm_block;
macro_rules! mad {
($x: tt, $y: tt) => {
asm_block! {
mul $x, $y;
lea $x, [$x + $y];
}
};
}
#[rustfmt::skip::macros(mad)]
unsafe fn f() -> u64 {
let mut x = 20;
asm!(
mad!({x}, 5),
x = inout(reg) x
);
x
}
现在我们能够进行如下调用:mad!({x}, rbx)
,mad!([rax], rbp)
,以及 mad!({x:e}, [rsp - 4])
。这看起来更整洁。
限制
- 由于 Rust 宏的标记化规则,不支持由
'
包围的字符串。 asm_block!
主要逐个消耗标记,所以如果汇编代码很长,可能会超出递归限制。当遇到错误时,用户需要使用#![recursion_limit = "<a_larger_value>"]
。rustfmt
会将mad!({x}, 5)
格式化为mad!({ x }, 5)
。虽然这不会对生成的汇编代码产生影响,但用户期望格式占位符时,阅读起来会感到困惑。用户可以使用#[rustfmt::skip::macros(mad)]
来防止rustfmt
格式化mad!
调用的内部。- 一些汇编器使用
;
作为注释起始符,但我们将其用作指令分隔符,因此汇编注释可能无法正常工作。强烈建议用户坚持使用 Rust 注释。 tt
无法捕获多个标记,因此为了使mad!(dword ptr [rax], ebp)
成为可能,需要改变mad!
的调用约定。例如
但是,必须使用use asm_block::asm_block; macro_rules! mad { ([{ $($x: tt)+ }], $y: tt) => { asm_block! { mul $($x)+, $y; lea $($x)+, [$($x)+ + $y]; } }; ($x: tt, $y: tt) => { mad!([{ $x }], $y) }; }
mad!
调用mad!([{ dword ptr [rax] }], ebp)
。- 目前,如果宏无法生成正确的汇编代码,我们无法提供手动注入汇编的逃生口。
许可证
根据您的选择,许可如下:
。