【发布时间】:2021-08-17 17:58:29
【问题描述】:
注意:为了简洁起见,这里的示例被简化了,因此它们不能证明我的意图。如果我只是像示例中一样写入内存位置,那么 C 将是最好的方法。但是,我正在做的事情在这个例子中我不能使用 C,即使通常最好留在 C 中。
我正在尝试使用值加载寄存器,但我坚持使用 8 位立即数。
我的代码:
https://godbolt.org/z/8EE45Gerd
#include <cstdint>
void a(uint32_t value) {
*(volatile uint32_t *)(0x21014) = value;
}
void b(uint32_t value) {
asm (
"push ip \n\t"
"mov ip, %[gpio_out_addr_high] \n\t"
"lsl ip, ip, #8 \n\t"
"add ip, %[gpio_out_addr_low] \n\t"
"lsl ip, ip, #2 \n\t"
"str %[value], [ip] \n\t"
"pop ip \n\t"
:
: [gpio_out_addr_low] "I"((0x21014 >> 2) & 0xff),
[gpio_out_addr_high] "I"((0x21014 >> (2+8)) & 0xff),
[value] "r"(value)
);
}
// adding -march=ARMv7E-M will not allow 16-bit immediate
// void c(uint32_t value) {
// asm (
// "mov ip, %[gpio_out_addr] \n\t"
// "str %[value], [ip] \n\t"
// :
// : [gpio_out_addr] "I"(0x1014),
// [value] "r"(value)
// );
// }
int main() {
a(20);
b(20);
return 0;
}
当我编写 C 代码(参见 a())时,它会在 Godbolt 中组装成:
a(unsigned char):
mov r3, #135168
str r0, [r3, #20]
bx lr
我认为它使用MOV 作为伪指令。当我想在汇编中做同样的事情时,我可以将值放入某个内存位置并使用LDR 加载它。我认为这就是我使用 -march=ARMv7E-M 时 C 代码的组装方式(MOV 被 LDR 替换),但是在许多情况下这对我来说并不实用,因为我会用它来做其他事情.
在 0x21014 地址的情况下,前 2 位为零,因此当我正确移位时,我可以将这个 18 位数字视为 16 位,这就是我在 b() 中所做的,但仍然我必须用 8 位立即数传递它。但是,在 Keil 文档中,我注意到提到了 16 位立即数:
https://www.keil.com/support/man/docs/armasm/armasm_dom1359731146992.htm
https://www.keil.com/support/man/docs/armasm/armasm_dom1361289878994.htm
在 ARMv6T2 及更高版本中,ARM 和 Thumb 指令集都包括:
A MOV instruction that can load any value in the range 0x00000000 to 0x0000FFFF into a register. A MOVT instruction that can load any value in the range 0x0000 to 0xFFFF into the most significant half of a register, without altering最低有效一半的内容。
我认为我的 CortexM4 应该是 ARMv7E-M 并且应该满足这个“ARMv6T2 及更高版本”的要求并且应该能够使用 16 位立即数。
但是从 GCC 内联汇编文档中我没有看到这样的提及:
https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html
当我启用 ARMv7E-M 架构并取消注释 c() 时,我使用常规“I”立即然后我得到一个编译错误:
<source>: In function 'void c(uint8_t)':
<source>:29:6: warning: asm operand 0 probably doesn't match constraints
29 | );
| ^
<source>:29:6: error: impossible constraint in 'asm'
所以我想知道有没有办法在 GCC 内联汇编中使用 16 位立即数,还是我遗漏了什么(这会使我的问题无关紧要)?
附带问题,是否可以在 Godbolt 中禁用这些伪指令?我已经看到它们也与 RISC-V 程序集一起使用,但我更愿意查看反汇编的真实字节码,以了解这些伪/宏程序集指令产生的确切指令。
【问题讨论】:
-
可以使用小写的
i约束。这不会进行范围检查,但对于您的目的应该没问题。此外,如果您需要寄存器中的值,您可以将该寄存器用作输入并将加载留给编译器。如果你使用"r"(0x1014),gcc 会很乐意为你生成一个movw r3, #4116。 -
@Jester 非常感谢你,它们都工作得很好:godbolt.org/z/84ME1hnar 所以我可以使用更合适的那个,我认为在 C 中加载它而不是强迫太多的手编译器可能最适合我的大多数情况。
-
使用输入值并让编译器这样做的另一个好处是编译器知道寄存器中的值。如果它需要再次使用它,它不必重新加载它。如果你修改你的godbolt链接做
d32(20); d32(20); d32(20);,你会得到movw r3, #4116 ; movs r2, #20 ; str r2, r3 ; str r2, r3 ; str r2, r3,而如果你在asm中做mov,它总是必须做负载。 -
是的,我明白你的意思。谢谢。
-
回复您对您现在已删除的答案的评论:未来的内联 asm 初学者读者不会意识到它是如何简化的/放回哪个重要部分。GNU C inline asm在不被不安全的坏例子误导的情况下很难学习,所以我反对或编辑任何不安全的 SO 答案。完全有可能使用内联汇编编写一个将字节、半字或字正确存储到 MMIO 寄存器的小型包装函数。如果您进行了一些小的更改来修复错误,我会删除我的反对票,也许还会反对。
标签: assembly gcc arm inline-assembly immediate-operand