Original article: http://www.swansontec.com/sregisters.html

Мистецтво вибірки Intel реєстрів

Я написав цю статтю для онлайн-журналу Scene Zine . Scene Zine обслуговує демонстраційну сцену, яка є спільнотою цифрового мистецтва, яка прагне висувати межі комп'ютерів через суміш музики, мистецтва та комп'ютерного програмування.Особлива категорія демонстраційних виробництв, 4K-інтрографії, зосереджується на вихідному файлі кінцевого виробництва. Мета полягає в тому, щоб створити максимально високоякісну музику, графіку та анімацію на 4096 байт. Для цього потрібні високоспеціалізовані методи оптимізації розміру, оскільки 4096 байт - менший простір, ніж дві сторінки набраного тексту або значок справжнього кольору Windows XP. У цій статті розглядаються деякі з цих методів.

Деякі люди прокоментували, що хочуть бачити більше експертних статей про програму в Scene Zine. Щоб виправити ситуацію, ця стаття стосується всіх програмістів мовлення.Тут обговорюється образотворче мистецтво збору, який реєструє використовувати у вашому коді. Ця інформація повинна спростити кодування та допомогти вам написати менші процедури.

Коли інженери Intel розробили оригінальний процесор 8086, вони мають особливе призначення для кожного реєстру. Оскільки вони розробили набір інструкцій, вони створили безліч оптимізацій та спеціальних вказівок на основі функції, яку вони очікували для виконання кожного реєстру. Використання регістрів за оригінальним планом Intel дозволяє коду повною мірою використовувати ці оптимізації. На жаль, це, здається, втрачене мистецтво. Кілька кодів знають про загальний дизайн Intel, і більшість компіляторів є надто спрощеними або зосереджені на швидкості виконання, щоб правильно використовувати регістри. Однак розуміння того, як встановлюються набори регістрів та інструкцій, є важливим кроком на шляху до легкого кодування розміру.

Використання реєстрів послідовно має інші переваги, крім оптимізації розміру. Як і використання хороших імен змін, використання послідовних регістрів робить код більш читабельним. Коли вони використовуються належним чином, регістри мають значення майже так само ясно, як лічильник циклів i , на мовах вищого рівня. Фактично, я періодично називаю свої змінні в C за x86-регістрами, оскільки імена реєстрів є настільки описовими. З правильним використанням реєстрації x86-ассемблер може бути майже як самодокументація як мова високого рівня.

Іншою перевагою, яка послідовно використовує реєстр, є краще стиснення. У виробництві, які використовують компресор для упакування остаточного збірки, наприклад, 4K інтрографії, створюючи більше зайвого коду, призводить до менших упакованих розмірів. Коли код використовує реєстри послідовно, одні й ті ж послідовності інструкцій починають з'являтися знову і знову. Це, в свою чергу, покращує коефіцієнт стиснення.

Як огляд, всі процесорні сімейства x86 мають 8 універсальних регістрів. Реєстри шириною 32 біти, хоча 16-бітні версії також доступні за допомогою спеціального префікса інструкцій з одним байтом. У 16-розрядному режимі ситуація змінюється.Нижче 16 біт доступні за замовчуванням, а повні регістри доступні лише з префіксним байтом.

Кожне зареєстроване ім'я дійсно є абревіатурою. Це справедливо навіть для "алфавітних" регістрів EAX, EBX, ECX та EDX. У наведеному нижче списку показані імена реєстрів та їх значення:

  • EAX - Реєстр акумуляторів
  • EBX - Базовий реєстр
  • ECX - лічильник реєстрації
  • EDX - Реєстр даних
  • ESI - Джерело індексу
  • EDI - індекс призначення
  • EBP - базовий вказівник
  • ESP - стековий покажчик

Окрім повнорозмірних загальних регістрів, процесор x86 також має вісім байтових регістрів. Оскільки ці регістри відображують безпосередньо в EAX, EBX, ECX та EDX, більшість людей розглядають їх як частини більших регістрів. Проте, з точки зору інструкцій, 8-бітні регістри є окремими сутностями. Наприклад, регістри CL та CH не користуються жодним корисними властивостями регістру ECX. За винятком AL і AH, жоден з 8-бітних регістрів не має особливого значення в наборі інструкцій, тому ця стаття не згадує їх.

EAX: акумулятор

Існує три основні архітектури процесорів: регістр, стек та акумулятор. У архітектурі реєстру такі операції, як додавання або віднімання, можуть відбуватися між будь-якими двома довільними регістрами. У архітектурі стека операції відбуваються між вершиною стека та іншими елементами в стеку. У акумуляторній архітектурі процесор має єдиний розрахунковий регістр, який називається акумулятором. Всі розрахунки відбуваються в акумуляторі, а інші регістри виконують функції простого зберігання даних.

Очевидно, процесор x86 не має акумуляторної архітектури. Вона, однак, має акумуляторний регістр: EAX / AL. Хоча більшість розрахунків може відбуватися між будь-якими двома регістрами, набір інструкцій дає спеціальну перевагу акумулятора як регістр обчислень. Наприклад, всі 9 основних операцій (ADD, ADC, AND, CMP, OR, SBB, SUB, TEST та XOR) мають спеціальні однобайтові коди для операцій між акумулятором і константою. Спеціалізовані операції, такі як множення, розподіл, розширення знаку та корекція BCD можуть відбуватися тільки в акумуляторі.

Оскільки більшість розрахунків відбуваються в акумуляторі, архітектура x86 містить багато оптимізованих інструкцій для переміщення даних в цей регістр та з нього. Для початку процесор має коди операцій XCHG для шістнадцяти байт для обміну даними між акумулятором та будь-яким іншим реєстром. Це не надто корисно, але вони показують, наскільки сильно інженери Intel віддавали перевагу акумулятору над іншими регістрами.Для них було краще поміняти дані в акумулятор, ніж працювати з ним, де це було.Іншими інструкціями, які переміщують дані в акумулятор і вивозяться з неї, є ЛОДИ, СТОС, ВНУТРІШНЬ, ВИХІД, ІНС, ВІДХОД, SCAS та XLAT. Нарешті, інструкція MOV має спеціальний однобайтовий код для переміщення даних в акумулятор з постійного місця пам'яті.

У своєму коді спробуйте виконати якомога більше роботи в акумуляторі. Як ви побачите, решта сім реєстрів загального призначення існують, перш за все, для підтримки обчислення, що відбувається в акумуляторі.

EDX: Реєстр даних

З семи інших реєстрів загального призначення реєстр даних EDX найбільш тісно пов'язаний з акумулятором. Інструкції, що стосуються елементів даних, що перевищують розміри, такі як множення, розподіл, CWD та CDQ, зберігають найважливіші біти в регістрі даних та найменш значні біти в акумуляторі. У певному сенсі реєстр даних - це 64-розрядне розширення акумулятора. Реєстр даних також відіграє роль у інструкціях з внутрішнього контролю. У цьому випадку акумулятор зберігає дані для читання або запису з порту, а в регістрі даних зберігається адреса порту.

У вашому коді реєстр даних є найбільш корисним для зберігання даних, пов'язаних з розрахунком акумулятора. На мій досвід, більшість розрахунків потребують лише цих двох регістрів для зберігання, якщо вони написані належним чином.

ECX: реєстр графа

Реєстр кол, ECX, є x86-еквівалентом повсюдної змінної i. Кожна підказка, пов'язана з підрахунком, в x86 використовує ECX. Найбільш очевидними підрахунками є LOOP, LOOPZ та LOOPNZ. Інша інтерактивна інструкція - це JCXZ, який, як випливає з назви, стрибає, коли лічильник дорівнює 0. Реєстраційний рахунок також відображається в деяких операціях біт-зміщення, де він містить кількість зсувів для виконання. Нарешті, регістр графів контролює рядки інструкцій через префікси REP, REPE і REPNE. У цьому випадку реєстр графів визначає максимальну кількість разів, коли операція буде повторитися.

Особливо в демо-версіях більшість розрахунків відбувається в циклі. У таких ситуаціях ECX є логічним вибором лічильника циклів, оскільки жоден інший реєстр не має так багато операцій розгалуження. Єдина проблема полягає в тому, що цей регістр підраховується вниз, а не на висоті мов. Однак проектування підрахунку вниз не є складним, тому це лише невелика складність.

EDI: Індекс призначення

Кожен цикл, який генерує дані, повинен зберігати результат у пам'яті, і для цього потрібен рухомий покажчик. Індикатор призначення, EDI, це покажчик. Індекс призначення містить припустиму адресу запису всіх операцій рядка. Найбільш корисною інструкцією по рядку, надзвичайно достатньо, є рідко використовується STOS. STOS копіює дані з акумулятора в пам'ять та збільшує індекс призначення. Ця однобайтова інструкція ідеальна, оскільки кінцевий результат будь-якого обчислення повинен бути в акумуляторі, і збереження результатів в рухомій пам'яті є загальним завданням.

Багато кодерів трактують індикатор призначення не більше, ніж додаткове місце для зберігання. Це помилка. Всі процедури повинні зберігати дані, і деякий регістр повинен слугувати покажчиком зберігання. Оскільки цільовий індекс призначений для цієї роботи, його використання для додаткового зберігання є відпрацьованим. Використовуйте стек або інший реєстр для зберігання та використовуйте EDI як глобальний курсор запису.

ESI: Індекс джерела

Індекс джерела ESI має ті самі властивості, що й індекс призначення. Різниця лише в тому, що індекс джерела для читання, а не для написання. Хоча всі процедури обробки даних записуються, не всі читаються, тому індекс джерела не є таким універсально корисним. Однак, коли прийшов час використовувати його, індекс джерела є настільки ж потужним, як і індекс призначення, і має такий самий тип інструкцій.

У ситуаціях, коли ваш код не читає будь-які дані, звичайно, використання індексу джерела для зручного місця зберігання є прийнятним.

ESP і EBP: покажчик стеку та базовий вказівник

З восьми восьми регістрів загального призначення, лише покажчик стеку, ESP та базовий покажчик EBP широко використовуються для їх первісної мети. Ці два регістри є основою механізму викликів функції x86. Коли блок коду викликає функцію, він виштовхує параметри та адресу повернення в стек. Після того, як функція inside, функція встановлює базовий покажчик, який дорівнює покажчику стека, а потім розміщує власні внутрішні змінні в стеку. З цього моменту функція посилається на її параметри та змінні відносно базового покажчика, а не на покажчик стека. Чому не стрілка вказівника? З якоїсь причини стек-вказівник зловживає режимами адресації. У 16-розрядному режимі воно взагалі не може бути зміщенням квадратної дужки. У 32-розрядному режимі він може відображатися у квадратних дужках лише шляхом додавання дороги SIB до коду операцій.

У вашому коді ніколи не буває причин використовувати покажчик стека для будь-якого іншого, ніж стек. Базовий вказівник, однак, для захоплень. Якщо ваша процедура передає параметри за регістром, а не за стеком (вони повинні), немає підстав скопіювати покажчик стека у базовий покажчик. Базовий покажчик стає вільним реєстром для всього, що вам потрібно.

EBX: Реєстр бази

У 16-розрядному режимі базовий регістр EBX працює як покажчик загального призначення. Окрім спеціалізованих регістрів ESI, EDI та EBP, це єдиний реєстр загального призначення, який може з'явитися в доступі для прямокутної пам'яті (наприклад, MOV [BX], AX). У 32-бітовому світі, однак, будь-який регістр може служити зміщенням пам'яті, тому основний регістр більше не є спеціальним.

Базовий реєстр отримує свою назву з інструкції XLAT. XLAT шукає значення в таблиці, використовуючи AL як індекс та EBX як базу. XLAT еквівалентно MOV AL, [BX + AL], що іноді корисно, якщо вам потрібно замінити одне 8-бітове значення на інше з таблиці (Подумайте про пошук кольору).

Отже, з усіх регістрів загального призначення EBX є єдиним реєстром без важливої ​​спеціальної мети. Це хороше місце для зберігання додаткового покажчика або розрахункового кроку, але не набагато більше.

Висновок

Вісім загальних реєстрів у сімействі процесорів x86 мають унікальну мету. Кожен реєстр має спеціальні вказівки та коди операцій, які роблять виконання цієї мети більш зручними та ефективними. Реєстри та їх використання коротко показані нижче:

  • EAX - всі основні розрахунки проводяться в EAX, що робить його схожим на спеціальний акумуляторний реєстр.
  • EDX - Реєстр даних - це розширення акумулятора. Це найбільш корисно для зберігання даних, пов'язаних з поточним розрахунком акумулятора.
  • ECX - Як змінна i на мовах високого рівня, register count є універсальним лічильником циклів.
  • EDI - Кожен цикл повинен десь зберігати свій результат, а індекс призначення вказує на це місце. За допомогою однієї байтівної інструкції STOS для запису даних з акумулятора цей регістр робить операції з даними набагато більш ефективними.
  • ESI - у циклі, які обробляють дані, індекс джерела зберігає розташування потоку вхідних даних. Як і індекс призначення, у EDI є зручна однобайтова інструкція для завантаження даних з пам'яті в акумулятор.
  • ESP - ESP - це священний покажчик стеку. Завдяки важливим інструкціям PUSH, POP, CALL та RET, що вимагають його значення, ніколи нема вагомих причин для використання покажчика стека для чогось іншого.
  • EBP - У функціях, що зберігають параметри або змінні в стеку, основний покажчик зберігає розташування поточного фрейма стека. В інших ситуаціях, однак, EBP - це безкоштовний реєстр даних.
  • EBX - у 16-розрядному режимі базовий регістр був корисний як покажчик. Тепер це абсолютно безкоштовно для додаткового місця для зберігання.

Як приклад того, як ці регістри вписуються разом, тут наведено опис типової процедури:

 mov esi, source_address
mov edi, destination_address
mov ecx, loop_count
my_loop: lodsd
;Do some calculations with eax here.
 stosd
loop my_loop

У цьому прикладі ECX є лічильником циклу, ESI вказує на вхідні дані, а EDI вказує на вихідні даніДеякі розрахунки, такі як розмиття, фільтр або, можливо, пошук кольорів відбуваються в циклі, використовуючи EAX як зміннуЦей приклад трохи спрощений, але, сподіваюсь, це показує загальну ідею. Справжня процедура, ймовірно, матиме справу з набагато складнішими даними, ніж DWORD, і, ймовірно, включатиме купу плаваючою точками.

На закінчення, використання реєстрів, як Intel, має кілька переваг. У першому випадку це дозволяє коду скористатися перевагами багатьох оптимізацій та спеціальних інструкцій.Це також робить код більш читабельним, оскільки регістри виконують передбачувані функції. Нарешті, використання реєстрів послідовно призводить до кращого стиснення шляхом просування більш повторюваних послідовностей інструкцій.

Авторське право © 2003 Уїльям Свонсон