阅读 CF、PF、ZF、SF、OF
Reading CF, PF, ZF, SF, OF
我正在为我自己的汇编语言编写一个虚拟机,我希望能够设置进位、奇偶校验、零、符号和溢出标志,因为它们在 x86-64 架构中设置,当我执行诸如作为补充。
备注:
- 我正在使用 Microsoft Visual C++ 2015 和 Intel C++ 编译器 16.0
- 我正在编译为 Win64 应用程序。
- 我的虚拟机(目前)只对 8 位整数进行运算
- 我(目前)对任何其他标志(例如 AF)不感兴趣
我当前的解决方案是使用以下函数:
void update_flags(uint16_t input)
{
Registers::flags.carry = (input > UINT8_MAX);
Registers::flags.zero = (input == 0);
Registers::flags.sign = (input < 0);
Registers::flags.overflow = (int16_t(input) > INT8_MAX || int16_t(input) < INT8_MIN);
// I am assuming that overflow is handled by trunctation
uint8_t input8 = uint8_t(input);
// The parity flag
int ones = 0;
for (int i = 0; i < 8; ++i)
if (input8 & (1 << i) != 0) ++ones;
Registers::flags.parity = (ones % 2 == 0);
}
为了添加,我将按如下方式使用:
uint8_t a, b;
update_flags(uint16_t(a) + uint16_t(b));
uint8_t c = a + b;
编辑:
澄清一下,我想知道是否有更多 efficient/neat 的方法(例如直接访问 RFLAGS)
此外,我的代码可能不适用于其他操作(例如乘法)
编辑 2 我现在已经将我的代码更新为:
void update_flags(uint32_t result)
{
Registers::flags.carry = (result > UINT8_MAX);
Registers::flags.zero = (result == 0);
Registers::flags.sign = (int32_t(result) < 0);
Registers::flags.overflow = (int32_t(result) > INT8_MAX || int32_t(result) < INT8_MIN);
Registers::flags.parity = (_mm_popcnt_u32(uint8_t(result)) % 2 == 0);
}
还有一个问题,我的进位标志代码能正常工作吗?我也希望它能正确设置减法期间发生的 "borrows"。
注意:我正在虚拟化的汇编语言是我自己设计的,旨在简单并基于 Intel 的 x86-64(即 Intel64)实现,因此我希望这些标志的行为大致相同方式。
TL:DR: 使用惰性标志评估,见下文。
input
是个奇怪的名字。大多数 ISA 根据操作的 结果 更新标志,而不是输入。您正在查看 8 位操作的 16 位结果,这是一种有趣的方法。在 C 中,您应该只使用 unsigned int
,保证至少为 uint16_t
。它将在 x86 上编译成更好的代码,其中 unsigned
是 32 位。 16 位操作需要一个额外的前缀,并可能导致部分寄存器速度变慢。
这可能有助于解决您提到的 8bx8b->16b mul 问题,具体取决于您希望如何在您正在模拟的架构中为 mul 指令定义标志更新。
我认为您的溢出检测不正确。请参阅 this tutorial linked from the x86 标记 wiki 以了解它是如何完成的。
这可能不会编译成非常快的代码,尤其是奇偶校验标志。您需要 emulating/designing 的 ISA 来设置奇偶校验标志吗?你从来没有说过你在模拟 x86,所以我假设这是你自己设计的玩具架构。
一个高效的模拟器(尤其是需要支持奇偶校验标志的模拟器)可能会从某种惰性标志评估中获益良多。保存一个值,如果需要,您可以从中计算标志,但在您到达读取标志的指令之前不要实际计算任何东西。大多数指令只写标志而不读取它们,它们只是将 uint16_t
结果保存到您的架构状态中。标志读取指令可以从保存的 uint16_t
中仅计算他们需要的标志,或者计算所有标志并以某种方式存储。
假设您无法让编译器实际从结果中读取 PF
,您可以尝试 _mm_popcnt_u32((uint8_t)x) & 1
。或者,将所有位水平异或在一起:
x = (x&0b00001111) ^ (x>>4)
x = (x&0b00000011) ^ (x>>2)
PF = (x&0b00000001) ^ (x>>1) // tweaking this to produce better asm is probably possible
我怀疑任何主要的编译器都可以将对结果的一堆检查优化为 LAHF
+ SETO al
或 PUSHF
。编译器可以导入using a flag condition to detect integer overflow to implement saturating addition, for example。但是弄清楚你想要所有的标志,并实际使用 LAHF
而不是一系列 setcc
指令,可能是不可能的。当编译器可以使用 LAHF
时,它需要一个模式识别器,而且可能没有人实现它,因为用例非常罕见。
没有 C/C++ 方法可以直接访问操作的标志结果,这使得 C 成为实现此类功能的不佳选择。 IDK 如果任何其他语言确实有标志结果,除了 asm.
我希望您可以通过在 asm 中编写部分仿真来获得很多性能,但这将是特定于平台的。更重要的是,它需要更多的工作。
我似乎已经解决了这个问题,方法是将更新标志的参数拆分为一个未签名和已签名的结果,如下所示:
void update_flags(int16_t unsigned_result, int16_t signed_result)
{
Registers::flags.zero = unsigned_result == 0;
Registers::flags.sign = signed_result < 0;
Registers::flags.carry = unsigned_result < 0 || unsigned_result > UINT8_MAX;
Registers::flags.overflow = signed_result < INT8_MIN || signed_result > INT8_MAX
}
对于加法(它应该为有符号和无符号输入产生正确的结果)我会做以下事情:
int8_t a, b;
int16_t signed_result = int16_t(a) + int16_t(b);
int16_t unsigned_result = int16_t(uint8_t(a)) + int16_t(uint8_t(b));
update_flags(unsigned_result, signed_result);
int8_t c = a + b;
和有符号乘法我会做以下事情:
int8_t a, b;
int16_t result = int16_t(a) * int16_t(b);
update_flags(result, result);
int8_t c = a * b;
以此类推更新标志的其他操作
注意:我在这里假设 int16_t(a)
符号扩展,int16_t(uint8_t(a))
零扩展。
我还决定不使用奇偶校验标志,如果我稍后改变主意,我的 _mm_popcnt_u32
解决方案应该会起作用..
P.S。感谢所有回复的人,这非常有帮助。另外,如果有人能发现我的代码中的任何错误,我们将不胜感激。
我正在为我自己的汇编语言编写一个虚拟机,我希望能够设置进位、奇偶校验、零、符号和溢出标志,因为它们在 x86-64 架构中设置,当我执行诸如作为补充。
备注:
- 我正在使用 Microsoft Visual C++ 2015 和 Intel C++ 编译器 16.0
- 我正在编译为 Win64 应用程序。
- 我的虚拟机(目前)只对 8 位整数进行运算
- 我(目前)对任何其他标志(例如 AF)不感兴趣
我当前的解决方案是使用以下函数:
void update_flags(uint16_t input)
{
Registers::flags.carry = (input > UINT8_MAX);
Registers::flags.zero = (input == 0);
Registers::flags.sign = (input < 0);
Registers::flags.overflow = (int16_t(input) > INT8_MAX || int16_t(input) < INT8_MIN);
// I am assuming that overflow is handled by trunctation
uint8_t input8 = uint8_t(input);
// The parity flag
int ones = 0;
for (int i = 0; i < 8; ++i)
if (input8 & (1 << i) != 0) ++ones;
Registers::flags.parity = (ones % 2 == 0);
}
为了添加,我将按如下方式使用:
uint8_t a, b;
update_flags(uint16_t(a) + uint16_t(b));
uint8_t c = a + b;
编辑: 澄清一下,我想知道是否有更多 efficient/neat 的方法(例如直接访问 RFLAGS) 此外,我的代码可能不适用于其他操作(例如乘法)
编辑 2 我现在已经将我的代码更新为:
void update_flags(uint32_t result)
{
Registers::flags.carry = (result > UINT8_MAX);
Registers::flags.zero = (result == 0);
Registers::flags.sign = (int32_t(result) < 0);
Registers::flags.overflow = (int32_t(result) > INT8_MAX || int32_t(result) < INT8_MIN);
Registers::flags.parity = (_mm_popcnt_u32(uint8_t(result)) % 2 == 0);
}
还有一个问题,我的进位标志代码能正常工作吗?我也希望它能正确设置减法期间发生的 "borrows"。
注意:我正在虚拟化的汇编语言是我自己设计的,旨在简单并基于 Intel 的 x86-64(即 Intel64)实现,因此我希望这些标志的行为大致相同方式。
TL:DR: 使用惰性标志评估,见下文。
input
是个奇怪的名字。大多数 ISA 根据操作的 结果 更新标志,而不是输入。您正在查看 8 位操作的 16 位结果,这是一种有趣的方法。在 C 中,您应该只使用 unsigned int
,保证至少为 uint16_t
。它将在 x86 上编译成更好的代码,其中 unsigned
是 32 位。 16 位操作需要一个额外的前缀,并可能导致部分寄存器速度变慢。
这可能有助于解决您提到的 8bx8b->16b mul 问题,具体取决于您希望如何在您正在模拟的架构中为 mul 指令定义标志更新。
我认为您的溢出检测不正确。请参阅 this tutorial linked from the x86 标记 wiki 以了解它是如何完成的。
这可能不会编译成非常快的代码,尤其是奇偶校验标志。您需要 emulating/designing 的 ISA 来设置奇偶校验标志吗?你从来没有说过你在模拟 x86,所以我假设这是你自己设计的玩具架构。
一个高效的模拟器(尤其是需要支持奇偶校验标志的模拟器)可能会从某种惰性标志评估中获益良多。保存一个值,如果需要,您可以从中计算标志,但在您到达读取标志的指令之前不要实际计算任何东西。大多数指令只写标志而不读取它们,它们只是将 uint16_t
结果保存到您的架构状态中。标志读取指令可以从保存的 uint16_t
中仅计算他们需要的标志,或者计算所有标志并以某种方式存储。
假设您无法让编译器实际从结果中读取 PF
,您可以尝试 _mm_popcnt_u32((uint8_t)x) & 1
。或者,将所有位水平异或在一起:
x = (x&0b00001111) ^ (x>>4)
x = (x&0b00000011) ^ (x>>2)
PF = (x&0b00000001) ^ (x>>1) // tweaking this to produce better asm is probably possible
我怀疑任何主要的编译器都可以将对结果的一堆检查优化为 LAHF
+ SETO al
或 PUSHF
。编译器可以导入using a flag condition to detect integer overflow to implement saturating addition, for example。但是弄清楚你想要所有的标志,并实际使用 LAHF
而不是一系列 setcc
指令,可能是不可能的。当编译器可以使用 LAHF
时,它需要一个模式识别器,而且可能没有人实现它,因为用例非常罕见。
没有 C/C++ 方法可以直接访问操作的标志结果,这使得 C 成为实现此类功能的不佳选择。 IDK 如果任何其他语言确实有标志结果,除了 asm.
我希望您可以通过在 asm 中编写部分仿真来获得很多性能,但这将是特定于平台的。更重要的是,它需要更多的工作。
我似乎已经解决了这个问题,方法是将更新标志的参数拆分为一个未签名和已签名的结果,如下所示:
void update_flags(int16_t unsigned_result, int16_t signed_result)
{
Registers::flags.zero = unsigned_result == 0;
Registers::flags.sign = signed_result < 0;
Registers::flags.carry = unsigned_result < 0 || unsigned_result > UINT8_MAX;
Registers::flags.overflow = signed_result < INT8_MIN || signed_result > INT8_MAX
}
对于加法(它应该为有符号和无符号输入产生正确的结果)我会做以下事情:
int8_t a, b;
int16_t signed_result = int16_t(a) + int16_t(b);
int16_t unsigned_result = int16_t(uint8_t(a)) + int16_t(uint8_t(b));
update_flags(unsigned_result, signed_result);
int8_t c = a + b;
和有符号乘法我会做以下事情:
int8_t a, b;
int16_t result = int16_t(a) * int16_t(b);
update_flags(result, result);
int8_t c = a * b;
以此类推更新标志的其他操作
注意:我在这里假设 int16_t(a)
符号扩展,int16_t(uint8_t(a))
零扩展。
我还决定不使用奇偶校验标志,如果我稍后改变主意,我的 _mm_popcnt_u32
解决方案应该会起作用..
P.S。感谢所有回复的人,这非常有帮助。另外,如果有人能发现我的代码中的任何错误,我们将不胜感激。