Начиная с процессора 80286 компания Intel поддерживала полную совместимость «снизу-вверх» в системе команд. То есть если какая-то из команд процессора дает такой-то результат на 8086, то и на более поздних процессорах результат будет точно таким же (сейчас не будем рассматривать ошибки типа неправильного деления в Pentium I).
Но так ли это? Что за вопрос! Ведь если бы совместимость не сохранялась, то старые программы не могли бы выполняться, а ведь до сих пор на любом компьютере можно поностальгировать запустив Norton Commander или Tetris . Однако не все так просто… Начиная с 8080 в процессорах Intel есть регистр флагов, состояние которого определяется результатом последней команды вычисления данных. Все флаги в нем давно описаны и поведение их строго зафиксировано. Кроме двух исключений.
AF - бит вспомогательного переноса. Он определял произошел ли перенос из младшей тетрады в старшую. Для 8-разрядного 8080 это было логично и для всех его команд состояние однозначно определялось. При переходе на 16-разрядные процессоры x86 появилась двойственность - в каком байте нужно учитывать вспомогательный перенос ? На что Intel однозначно дала ответ - флаг AF меняет свое состояние только по результатам переноса внутри младшего байта. Но процессоре 8080 не было команд умножения и деления. И при переходе на 16-разрядную архитектуру поведение AF при командах умножения и деления так и осталось не определенным!
Дальше еще интереснее, оказалось что по-разному ведет себя еще и флаг четности! На разных поколениях процессоров и у разных производителей в результате операций умножения и деления флаги AF и PF разные!
Чтобы убедиться в этом можно выполнить коротенькую программу на C++:
#include
#include
int main() {
using namespace std;
const int min_i = -20, max_i = 50,min_j=-10, max_j = 50;
short int i, j, k, pf;
cout << "Name;i;j;Result;AF;PF;Calculated parity\n";
for (i = min_i; i < max_i; i++) {
for (j = min_j; j < max_j; j++) {
__asm {
mov ax, i
mov dx, j
imul dx
pushf
pop ax
mov k, ax
}
pf = i * j; pf = ((pf >> 7) ^ (pf >> 6) ^ (pf >> 5) ^ (pf >> 4) ^ (pf >> 3) ^ (pf >> 2) ^ (pf >> 1) ^ pf ^ 1) & 1;
cout << "Mul ;" << setw(4) << i << "; " << setw(4) << j << "; " << setw(4) << i * j << "; " << ((k & 0x10) ? "1" : "0") << "; " << ((k & 0x04) ? "1" : "0") << "; " << pf <<"\n";
}
}
for (i = min_i; i < max_i; i++) {
for (j = min_j; j < max_j; j++) {
if (j == 0) continue;
__asm {
mov ax, i
mov cx, j
cwd
idiv cx
pushf
pop ax
mov k, ax
}
pf = i / j; pf = ((pf >> 7) ^ (pf >> 6) ^ (pf >> 5) ^ (pf >> 4) ^ (pf >> 3) ^ (pf >> 2) ^ (pf >> 1) ^ pf ^ 1) & 1;
std::cout << "Div ;" << setw(4) << i << "; " << setw(4) << j << "; " << setw(4) << i / j << "; " << ((k & 0x10) ? "1" : "0") << "; " << ((k & 0x04) ? "1" : "0") << "; " << pf << "\n";
}
}
}
Эта программа выводит на экран результат проверки флагов AF и PF после операций умножения и деления. А так же (для удобства) рассчитанный результат четности последнего байта результата операции.
Я протестировал этой программой 16 разных процессоров. Даже по такой небольшой выборке, можно сделать следующие выводы:
- Операции умножения различаются на современных и более старых процессорах Intel (до Wolfdale включительно - можно отнести к «старому» поколению, начиная с Sandy Bridge - «новое» (Nehalem мне не попадался)). На «новых» процессорах бит AF всегда устанавливается после умножения в 0, а PF - в соответствии с младшим байтом результата (как и следовало бы делать раньше). Логика же установки битов в «старых» процессорах мне не понятна.
- Процессоры AMD выполняют умножение с установкой битов, в точности как «старое» поколение от Intel.
- На операциях деления все процессоры Intel устанавливают бит четности одинаково (но он не совпадает с четностью битов последнего байтов результатов деления и остатка по модулю).
- Бит AF после деления в процессорах Intel устанавливается в 0, за исключением семейств Yonah и более ранних.
- AMD после деления устанавливает AF=1 и PF=0.
Скачать полную таблицу тестов:
Папка из Облака Mail.ru
Облако Mail.ru - это ваше персональное надежное хранилище в интернете.
cloud.mail.ru