读CF,PF,ZF,SF,OF

时间:2016-03-26 05:33:14

标签: c++ x86-64 icc

我正在为自己的汇编语言编写虚拟机,我希望能够设置x86-64架构中设置的进位,奇偶校验,零,符号和溢出标志,当我执行添加等操作时

注意:

  • 我正在使用Microsoft Visual C ++ 2015&英特尔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;

编辑: 为了澄清,我想知道是否有更有效/更好的方法(例如直接访问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);
}

还有一个问题,我的进位标志代码是否正常工作?我还希望它能正确设置为&#34;借用&#34;在减法期间发生。

注意:我正在虚拟化的汇编语言是我自己设计的,意味着简单,基于英特尔的x86-64(即Intel64)的实现,所以我希望这些标志主要表现在同样的方式。

2 个答案:

答案 0 :(得分:1)

TL:DR :使用懒惰标记评估,见下文。

input是一个奇怪的名字。大多数ISA根据操作的结果更新标志,而不是输入。您正在查看8位操作的16位结果,这是一种有趣的方法。在C中,您应该使用unsigned int,保证至少为uint16_t。它将在x86上编译为更好的代码,其中unsigned为32位。 16位操作采用额外的前缀,可能导致部分寄存器减速。

这可能有助于解决您注意到的8bx8b-> 16b mul问题,具体取决于您希望如何定义您正在模拟的架构中的mul指令的标志更新。

我不认为您的溢出检测是正确的。请参阅this tutorial代码wiki中链接的,了解其完成情况。

这可能无法编译成非常快的代码,尤其是奇偶校验标志。您是否需要您正在仿效/设计的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 alPUSHF。编译器可以被引入using a flag condition to detect integer overflow to implement saturating addition, for example。但让它想出你想要所有的标志,并且实际上使用LAHF而不是一系列setcc指令,可能是不可能的。编译器需要一个模式识别器才能使用LAHF,并且可能没有人实现,因为用例非常罕见。

没有C / C ++方法来直接访问操作的标志结果,这使得C成为实现这样的东西的不良选择。 IDK,如果任何其他语言确实有标志结果,而不是asm。

我希望你可以通过在asm中编写部分仿真来获得很多性能,但这将是特定于平台的。更重要的是,它还需要做更多的工作。

答案 1 :(得分:0)

我似乎解决了这个问题,将更新标志的参数拆分为无符号和签名的结果,如下所示:

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。谢谢所有回复的人,非常有帮助。此外,如果有人能够发现我的代码中的任何错误,那将不胜感激。