簡體   English   中英

讀取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;

編輯:澄清一下,我想知道是否有一種更有效/更好的方法來做到這一點(例如,直接訪問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);
}

還有一個問題,我的進位標志代碼能否正常工作?,我也希望為減法期間發生的“借方”正確設置它。

注意:我正在虛擬化的匯編語言是我自己設計的,意味着很簡單,並且基於Intel x86-64(即Intel64)的實現,因此我希望這些標志的行為方式大致相同。

TL:DR :使用惰性標志評估,請參見下文。


input是一個奇怪的名字。 大多數ISA根據操作結果而不是輸入來更新標志。 您正在查看8位操作的16位結果,這是一種有趣的方法。 在C語言中,您應該只使用unsigned int ,它保證至少為uint16_t 它將在32位unsigned x86上編譯為更好的代碼。 16位運算符使用額外的前綴,並且可能導致部分寄存器變慢。

這可能有助於解決您指出的8bx8b-> 16b mul問題,具體取決於您要如何為正在仿真的體系結構中的mul指令定義標志更新。

我認為您的溢出檢測不正確。 請參閱標簽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 例如,可以導致編譯器使用標志條件來檢測整數溢出以實現飽和加法 但是要弄清楚您想要所有標志,並實際上使用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解決方案應該可以使用。

PS:感謝所有回答的人,它非常有幫助。 另外,如果任何人都可以在我的代碼中發現任何錯誤,將不勝感激。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM