[英]Reading CF, PF, ZF, SF, OF
我正在為自己的匯編語言編寫虛擬機,我希望能夠在執行諸如加法之類的操作時設置進位,奇偶校驗,零,符號和溢出標志,因為它們是在x86-64體系結構中設置的。
筆記:
我當前的解決方案使用以下功能:
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指令定義標志更新。
我認為您的溢出檢測不正確。 請參閱x86標簽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 al
或PUSHF
。 例如,可以導致編譯器使用標志條件來檢測整數溢出以實現飽和加法 。 但是要弄清楚您想要所有標志,並實際上使用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.