你如何解释 gcc 对 i386 的 IN、OUT 指令的内联程序集约束?



据我所知,gcc 内联汇编中使用的约束告诉 gcc 输入和输出变量必须(或必须(去哪里才能生成有效的程序集。正如《精细手册》所说,"对操作数放置的限制"。

下面是教程中的一个具体工作示例。

static inline uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "Nd"(port) );
return ret;
}

inb是AT&T语法,表示从I/O端口接收一个字节的i386IN指令。

以下是此指令的规格,取自 i386 手册。请注意,端口号从0x00000xFFFF

IN AL,imm8  // Input byte from immediate port into AL
IN AX,imm8  // Input word from immediate port into AX
IN EAX,imm8 // Input dword from immediate port into EAX
IN AL,DX    // Input byte from port DX into AL
IN AX,DX    // Input word from port DX into AX
IN EAX,DX   // Input dword from port DX into EAX

给定像uint8_t x = inb(0x80);这样的语句,程序集输出正确地inb $0x80,%al.它使用了指令的IN AL,imm8形式。

现在,假设我只关心IN AL,imm8形式,从0x000xFF(含(之间的端口接收uint8_t。这与工作示例之间的唯一区别是,port现在是一个uint8_t模板参数(以使其有效地成为常量(,并且约束现在"N"

template<uint8_t port>
static inline uint8_t inb()
{
uint8_t ret;
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "N"(port) );
return ret;
}

失败!

我认为"N"约束意味着"对于此指令,您必须有一个常量的无符号 8 位整数",但显然并非如此,因为它是"不可能的约束"。uint8_t模板参数不是常量的无符号 8 位整数吗?

如果我将"N"替换为"Nd",则会出现不同的错误:

./test.h: Assembler messages:
./test.h:23: Error: operand type mismatch for `in'

在这种情况下,汇编器输出inb %dl, %al这显然是无效的。

为什么这仅适用于"Nd"uint16_t,而不适用于"N"uint8_t

编辑:

这是我在 godbolt.org 上尝试过的精简版本:

#include <cstdint>
template<uint8_t N>
class Port {
public:
uint8_t in() const {
uint8_t data;
asm volatile("inb %[port], %%al"
:  
:  [port] "N" (N)
:  // clobbers
);
return data;    
}
};
void func() {
Port<0x7F>().in();
}

有趣的是,这工作正常,除非您将 N 更改为 0x80 到 0xFF 之间的任何值。在 clang 上,这将生成"128 超出约束 N 的范围"错误。这会在 gcc 中生成更通用的错误。

根据约束的文档方式,代码应按预期工作。

一年多后,这似乎仍然是一个错误。编译器似乎正在将N从无符号值转换为有符号值,并尝试将其传递到内联程序集约束中。当传递到约束中的值无法表示为 8 位有符号值时,这当然会失败。假设输入约束"N"允许无符号 8 位值,并且应接受 0 到 255 (0xff( 之间的任何值:

N

无符号 8 位整数常量(用于输入和输出指令(。

GCC的bugzilla有一个类似的错误报告,标题为">常量约束检查符号扩展了无符号的常量输入操作数"。

在其中一个相关线程中,建议您可以通过 ANDING (&( 0xff常量(即:N & 0xff(来解决此问题。我还发现静态强制转换N到比uint8_t宽的无符号类型也有效:

#include <cstdint>
template<uint8_t N>
class Port {
public:
uint8_t in() const {
uint8_t data;
asm volatile("inb %[port], %0"
: "=a"(data)
:  [port] "N" (static_cast<uint16_t>(N))
:  // clobbers
);
return data;    
}
};
void func() {
Port<0x7f>().in();
Port<0x80>().in();
//    Port<0x100>().in();    // Fails as expected since it doesn't fit in a uint8_t
}

要测试这一点,您可以在 godbolt 上玩它。

最新更新