Чому вказівки x86-64 на 32-бітних регістрах дорівнюють нулю верхній частині повного 64-розрядного регістра?


119

У x86-64 екскурсії по посібниках Intel я читав

Мабуть, найдивовижніший факт полягає в тому, що така інструкція, як MOV EAX, EBXавтоматично нулює верхні 32 біти RAXрегістру.

Документація Intel (3.4.1.1 регістри загального призначення в 64-бітному режимі в посібнику з базової архітектури), цитована в тому ж джерелі, говорить нам:

  • 64-бітні операнди генерують 64-розрядний результат у регістрі загального призначення призначення.
  • 32-бітні операнди генерують 32-розрядний результат, розширений нулем до 64-розрядного результату в регістрі загального призначення призначення.
  • 8-бітні та 16-бітні операнди генерують 8-бітний або 16-розрядний результат. Верхні 56 біт або 48 біт (відповідно) регістра загального призначення призначення не змінюються операцією. Якщо результат 8-бітної або 16-бітної операції призначений для обчислення 64-бітової адреси, явно підпишіть-розгорніть реєстр до повних 64-біт.

У складі x86-32 та x86-64 16-бітні інструкції, такі як

mov ax, bx

не показуйте такої "дивної" поведінки, що верхнє слово eax дорівнює нулю.

Таким чином: яка причина, чому така поведінка була введена? На перший погляд це здається нелогічним (але причина може бути в тому, що я звик до химерності складання x86-32).


16
Якщо ви Google для "Часткової зупинки реєстру", ви знайдете досить багато інформації про проблему, яку вони (майже напевно) намагалися уникнути.
Джеррі Труну


4
Не просто "більшість". AFAIK, усі вказівки з r32операндом призначення нульових високих 32, а не злиттям. Наприклад, деякі монтажники замінити pmovmskb r64, xmmз pmovmskb r32, xmm, зберігаючи REX, тому що 64 - бітна версія призначення поводиться однаково. Незважаючи на те, що розділ "Операція" в посібнику перераховує всі 6 комбінацій джерела 32/64 біт і джерело 64/128 / 256b окремо, неявне розширення нуля форми r32 дублює явне розширення нуля форми r64. Мені цікаво щодо впровадження HW ...
Пітер Кордес

2
@HansPassant, починається кругова посилання.
kchoi

Відповіді:


98

Я не AMD або не виступаю за них, але я би зробив це так само. Оскільки обнулення високої половини не створює залежності від попереднього значення, CPU доведеться чекати. Механізм перейменування реєстру був би пошкоджений, якби це не було зроблено таким чином.

Таким чином, ви можете записувати швидкий код, використовуючи 32-розрядні значення в 64-бітному режимі, без необхідності весь час чітко розбивати залежності. Без такої поведінки кожна 32-розрядна інструкція в 64-бітному режимі повинна була б чекати на те, що відбулося раніше, хоча ця висока частина майже ніколи не буде використана. (Створення int64-розрядних даних втратить кеш-пам'ять і пропускну здатність пам'яті; x86-64 найбільш ефективно підтримує 32 та 64-бітні розміри операндів )

Дивна поведінка для 8 та 16-бітових розмірів операндів. Божевілля залежності є однією з причин того, що зараз уникають 16-бітних інструкцій. x86-64 успадкував це від 8086 для 8-бітових і 386 для 16-розрядних, і вирішив, щоб 8 і 16-бітні регістри працювали так само в 64-бітному режимі, як і в 32-бітному режимі.


Див. Також Чому GCC не використовує часткові регістри? для практичних деталей того, як записи до 8 та 16-бітових часткових регістрів (і наступних зчитувань повного реєстру) обробляються реальними процесорами.


8
Я не думаю, що це дивно, я думаю, що вони не хотіли занадто сильно ламати і зберігали там стару поведінку.
Олексій Фрунзе

5
@Alex, коли вони запровадили 32-бітний режим, старого поведінки не було. Раніше не було високої частини. Звичайно, після цього її більше не можна було змінити.
Гарольд

1
Я говорив про 16-бітні операнди, чому в цьому випадку верхні біти не нульові. Вони не в не 64-бітних режимах. І це зберігається і в 64-бітному режимі.
Олексій Фрунзе

3
Я інтерпретував вашу "Поведінка для 16-бітових інструкцій - дивна", як "дивно, що розширення з нулем не відбувається з 16-бітовими операндами в 64-бітному режимі". Звідси мої коментарі щодо збереження його таким же чином у 64-бітному режимі для кращої сумісності.
Олексій Фрунзе

8
@ Алекс ой я бачу. Гаразд. Я не думаю, що це дивно з цієї точки зору. Просто з точки зору "озираючись назад, можливо, це була не така гарна ідея". Здогадайтесь, я повинен був бути зрозумілішим :)
harold

9

Це просто економить місце в інструкціях та наборі інструкцій. Ви можете перемістити невеликі негайні значення до 64-розрядного реєстру, використовуючи існуючі (32-бітні) інструкції.

Це також позбавляє вас від необхідності кодувати 8 байтових значень для MOV RAX, 42, коли MOV EAX, 42їх можна повторно використовувати.

Ця оптимізація не настільки важлива для 8 та 16 бітових опцій (оскільки вони менші), а зміна правил там також порушить старий код.


7
Якщо це правильно, чи не було б більше сенсу підписувати-розширювати, а не 0 продовжувати?
Damien_The_Unbeliever

16
Розширення знаків повільніше, навіть у апаратному забезпеченні. Нульове розширення можна робити паралельно тому, що обчислення створюють нижню половину, але розширення знаків не можна робити, поки (принаймні, ознака) нижня половина не буде обчислена.
Джеррі Труну

13
Ще один пов'язаний трюк - використовувати, XOR EAX, EAXоскільки XOR RAX, RAXпотрібен був би префікс REX.
Ніл

3
@Nubok: Звичайно, вони могли б додати кодування movzx / movsx, яке бере негайний аргумент. Велика частина часу це більш зручно мати верхні біти обнуляються, так що ви можете використовувати значення в якості індексу масиву (бо все регістри повинні бути однаковим розміром в ефективному адресу: [rsi + edx]не допускається). Звичайно, уникнення помилкових залежностей / часткових реєстрів (інша відповідь) - ще одна основна причина.
Пітер Кордес

4
і зміна правил там також порушить старий код. Старий код не може працювати в 64-бітному режимі (наприклад, 1-байт inc / dec є префіксами REX); це не має значення. Причиною не очищення бородавок x86 є менша різниця між тривалим режимом та режимами compat / legacy, тому менше інструкцій доводиться розшифровувати по-різному в залежності від режиму. Компанія AMD не знала, що AMD64 піде назустріч, і, на жаль, дуже консервативна, тому для підтримки знадобиться менше транзисторів. Довгостроково, це було б добре, якби компілятори та люди повинні пам’ятати, які речі працюють по-різному в 64-бітному режимі.
Пітер Кордес

1

Без нуля, що поширюється на 64 біти, це означатиме, що читання інструкцій із rax2-х залежностей для її raxоперанду (інструкція, що записує, eaxта інструкція, що пише до raxнеї), це означає, що 1) ROB повинен мати записи для кілька залежностей для одного операнда, що означає, що ROB вимагатиме більше логіки та транзисторів і займає більше місця, а виконання буде повільніше очікувати на непотрібну другу залежність, яка може зайняти віки для виконання; або як варіант 2), що, напевно, відбувається з 16-бітовими інструкціями, етап розподілу, ймовірно, зупиняється (тобто, якщо RAT має активний розподіл для axзапису і з'являється eaxзчитування, він зупиняється, поки axзапис не вийде з ладу).

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

Єдиною перевагою розширення, що не враховує нуль, є забезпечення raxвключення бітів більш високого порядку , наприклад, якщо він спочатку містить 0xffffffffffffffffffff, результатом буде 0xffffffff00000007, але ISA дуже мало причин робити цю гарантію за такі витрати, і більш імовірно, що користі від нульового розширення насправді потрібно більше, тому це економить додатковий рядок коду mov rax, 0. Гарантуючи, що він завжди буде нульовим до 64 біт, компілятори можуть працювати з цією аксіомою, маючи на увазі mov rdx, rax, raxлише дочекавшись своєї єдиної залежності, це означає, що він може приступити до швидшого виконання та вийти на пенсію, звільнивши одиниці виконання. Крім того, він також дозволяє більш ефективні нульові ідіоми, як xor eax, eaxнуль, raxне вимагаючи байт REX.


Часткові прапорці на Skylake принаймні спрацьовують, маючи окремі входи для CF проти будь-якого з SPAZO. (Так cmovbe2 уп, але cmovb1). Але жоден процесор, який не перейменовує часткове реєстр, робить це так, як ви запропонували. Натомість вони вставляють об'єднуючий загально, якщо частковий регістр буде перейменований окремо від повного (тобто є "брудним"). Див. Чому GCC не використовує часткові регістри? і як саме виконують часткові регістри Haswell / Skylake? Здається, що написання АЛ має помилкову залежність від RAX, а АН непослідовна
Пітер Кордес

Процесори сімейства P6 або затримуються протягом ~ 3 циклів, щоб вставити об'єднується загалом (Core2 / Nehalem), або раніше сімейство P6 (PM, PIII, PII, PPro) просто затримуються протягом (принаймні?) ~ 6 циклів. Можливо, це так, як ви запропонували в 2, чекаючи, коли повне значення регістра буде доступним за допомогою запису на постійний / архітектурний файл реєстру.
Пітер Кордес

@PeterCordes ой, я знав про об'єднання уоп принаймні для часткових стоянок прапора. Має сенс, але я забув, як це працює на хвилину; натиснув один раз, але я забув робити нотатки
Льюїс Келсі,

@PeterCordes microarchitecture.pdf: This gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAXЯ не можу знайти приклад "злиття загалу", який би використовувався для вирішення цього питання, те саме для часткової зупинки прапора
Льюїс Келсі,

Право, ранній P6 просто зупиняється до моменту списання. Core2 і Nehalem вставляють об'єднується взагалі після / до? лише затримуючи передню частину на коротший час. Сендібрідж вставляє об'єднувальні упи без затримок. (Але AH-злиття повинно видаватись за цикл самостійно, тоді як злиття AL може бути частиною повної групи.) Haswell / SKL взагалі не перейменовує AL окремо від RAX, тому mov al, [mem]навантаження з мікроплавленням + ALU- злиття, лише перейменування AH, а AH-злиття взагалі все-таки видає одне. Механізми злиття часткового прапора в цих процесорах різняться, наприклад, Core2 / Nehalem все ще просто зупиняється на часткові прапори, на відміну від часткових reg.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.