Об одной недокументированной особенности умножения и деления на процессорах x86

May 11, 2021 12:30


Начиная с процессора 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 разных процессоров. Даже по такой небольшой выборке, можно сделать следующие выводы:

  1. Операции  умножения различаются на современных и более старых процессорах Intel  (до Wolfdale включительно - можно отнести к «старому» поколению, начиная  с Sandy Bridge - «новое» (Nehalem мне не попадался)). На «новых»  процессорах бит AF всегда устанавливается после умножения в 0, а PF - в  соответствии с младшим байтом результата (как и следовало бы делать  раньше). Логика же установки битов в «старых» процессорах мне не  понятна.
  2. Процессоры AMD выполняют умножение с установкой битов, в точности как «старое» поколение от Intel.
  3. На  операциях деления все процессоры Intel устанавливают бит четности  одинаково (но он не совпадает с четностью битов последнего байтов  результатов деления и остатка по модулю).
  4. Бит AF после деления в процессорах Intel устанавливается в 0, за исключением семейств Yonah и более ранних.
  5. AMD после деления устанавливает AF=1 и PF=0.

Скачать полную таблицу тестов:


Папка из Облака Mail.ru


Облако Mail.ru - это ваше персональное надежное хранилище в интернете.

cloud.mail.ru

#include, ассемблер, процессор, код

Previous post Next post
Up