Підвищення продуктивності додатків IBM z Systems з використанням вбудованого асемблера компілятора IBM XL C / C ++

  1. Таблиця 1. Призначення регістрів платформи z Systems під управлінням Linux
  2. Малюнок 1. Виділення 16 регістрів загального призначення окремо і парами
  3. код умови
  4. Лістинг 1. Інструкція розгалуження і код умови
  5. Лістинг 2. Компіляція і запуск коду з лістингу 1
  6. асемблерні інструкції
  7. Група алгебраїчних арифметичних інструкцій
  8. Лістинг 3. Модуль example02.c - вихідний код на мові C без асемблерних інструкцій
  9. Лістинг 4. Компіляція і запуск модуля example02.c
  10. Лістинг 5. Вихідний асемблерний код модуля example02.s без додаткових інструкцій
  11. Малюнок 2. Вміст стека після виконання команди з рядка 8
  12. Лістинг 6. Додавання ассемблерних інструкцій в код модуля example02.s
  13. Лістинг 7. Компіляція і запуск оновленого ассемблерного коду
  14. Лістинг 8. Модуль example02.c - додавання оператора вбудованого асемблера в код C
  15. Лістинг 9. Компіляція і запуск оновленого коду C з оператором вбудованого асемблера
  16. Група інструкцій порівняння
  17. Група інструкцій умовного розгалуження
  18. Таблиця 3. Зіставлення значень коду умови і розрядів маски
  19. Приклади інструкцій умовного розгалуження
  20. Лістинг 10. Оператори вбудованого асемблера, що містять інструкції порівняння і умовного розгалуження
  21. Таблиця 4. Зіставлення значень коду умови і значень маски
  22. Група інструкцій завантаження
  23. Лістинг 11. Приклад інструкції завантаження
  24. Лістинг 12. Використання іншого ассемблерной інструкції
  25. Група інструкцій збереження
  26. Лістинг 13. Приклад інструкції збереження
  27. Висновок
  28. Подякою
  29. ресурси

Довідник з основних функцій по асемблерним інструкціям, вбудовуваним в код додатків для мейнфреймів, що розробляються на C / C ++

Компілятор IBM XL C / C ++ для Linux на платформі z Systems версії 1, випущений в 2015 році, дозволяє безпосередньо вбудовувати асемблерні інструкції (асемблерний код) в додатки, що розробляються на мові C / C ++. Крім створення високооптимізовані коду і максимального використання апаратних можливостей, новий компілятор надає досвідченим розробникам велику гнучкість у використанні інструкцій на рівні процесора. За допомогою вбудованого асемблера розробники ПЗ можуть застосовувати асемблерний код в найбільш критичних частинах своїх програм на C / C ++, розкриваючи весь свій потенціал і забезпечуючи найкращу швидкодію своїх програм.

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

В асемблері використовуються 16 64-розрядних регістрів загального призначення з номерами від 0 до 15. Всі регістри загального призначення, за винятком регістра 0, можна використовувати для зберігання базових адрес або індексів при виконанні арифметичних операцій. В основних арифметичних і логічних операціях регістри загального призначення можна використовувати в якості акумуляторів. Призначення деяких регістрів загального призначення платформи z Systems під управлінням ОС Linux описано в таблиці 1.

Таблиця 1. Призначення регістрів платформи z Systems під управлінням Linux

Ім'я регістра Призначення r2, r3 Параметри, повернені значення r4, r5, r6 Параметри r13 Базовий регістр пулу литералов r14 Зворотна адреса r15 Покажчики стека

Регістри загального призначення з номерами 0-15 мають відповідні імена - від r0 до r15. Деякі інструкції (наприклад, MR - множення і DR - розподіл) вимагають використання в операнде двох суміжних регістрів. При виконанні таких операцій необхідно виділяти парному-непарну пару суміжних регістрів загального призначення. Для звернення до операнду реєстрової пари використовується регістр з парним номером. На малюнку 1 показано, як регістри загального призначення виділяються окремо і парами.

Малюнок 1. Виділення 16 регістрів загального призначення окремо і парами
Довідник з основних функцій по асемблерним інструкціям, вбудовуваним в код додатків для мейнфреймів, що розробляються на C / C ++   Компілятор IBM XL C / C ++ для Linux на платформі z Systems версії 1, випущений в 2015 році, дозволяє безпосередньо вбудовувати асемблерні інструкції (асемблерний код) в додатки, що розробляються на мові C / C ++

Крім регістрів загального призначення, у вбудованому асемблері використовуються регістри з плаваючою точкою і векторні регістри. Ці регістри будуть детально розглянуті в наступних статтях, присвячених вбудованому асемблеру.

код умови

Код умови (Condition Code, cc) - це 18-й і 19-й біти слова стану програми (PSW) процесора. Будучи двухбітовий значенням, код умови може приймати значення від 0 до 3 в залежності від результатів виконання певних інструкцій. Коли коду умови присвоюється якесь значення, воно зберігається в ньому до тих пір, поки не буде змінено інший інструкцією. Більшість арифметичних і логічних операцій, а також деякі інші інструкції, змінюють значення коду умови. Значення коду умови може впливати на прийняття інструкціями розгалуження рішення про передачу управління.

У прикладі з лістингу 1 демонструється використання коду умови на високому рівні. Більш докладно формат інструкцій і операторів вбудованого асемблера буде розглянуто в наступній статті цієї серії.

Лістинг 1. Інструкція розгалуження і код умови

1 #include <stdio.h> 2 3 void myAdd (int a, int b) {4 int noOverflow = 1; 5 asm ( "AR% 0,% 2 \ n" // При переповненні інструкція AR помістить в код умови значення 3 6 "BRC 0xE, OK \ n" // Інструкція BRC перевіряє код умови: якщо він не дорівнює 3 (переповнення НЕ було), у разі переходу до рядка 8, т. е. рядок 7 пропускається 7 "XR% 1,% 1 \ n" // Інструкція XR скидає значення змінної noOverflow 8 "OK: \ n" 9: "+ r" (a ), "+ r" (noOverflow) 10: "r" (b) 11: "cc" // Код умови поміщений в список екранування, щоб 12); // повідомити компілятору про те, що він може бути змінений 13 if (noOverflow) {14 printf ( "Sum is% d \ n", a); 15} else {16 printf ( "Overflow \ n"); 17} 18} 19 int main () {20 int a, b; 21 a = 11, b = -2; // Значення a = 11, b = -2 призводять до переповнення, 22 myAdd (a, b); // в цьому випадку виводиться повідомлення "Sum is 9". 23 a = 0x7fffffff, b = 0x7fffffff; // Надаємо змінним a і b великі значення, щоб виникло переповнення. 24 myAdd (a, b); // Виникне переповнення і виводиться повідомлення "Overflow". 25 return 0; 26}

За допомогою параметрів% 0,% 1 і% 2 ми звертаємося до першого (змінна a), другого (змінна noOverflow) і третього операндам (змінна b), зазначеним в рядках 9 і 10.

Інструкція ADD (AR) в рядку 5 встановлює значення коду умови наступним чином:

0Результат складання дорівнює нулю, переповнення немає.1Результат менше нуля, переповнення немає.2Результат більше нуля, переповнення немає.3Виникло переповнення.

У рядку 6 інструкція умовного розгалуження BRC перевіряє значення коду умови і визначає подальший шлях виконання програми. Якщо код умови містить значення 0, 1 або 2 (т. Е. Переповнення не виникло), то виконується перехід по мітці "OK" на рядок 8, а інструкція виключає АБО (XR) в рядку 7 пропускається. Якщо ж код умови містить значення 3 (ознака переповнення), то виконується наступна інструкція (XR) в рядку 7, яка скидає значення змінної noOverflow.

Якщо переповнення не виникло, то код з лістингу 1 виводить на екран суму змінних, в іншому випадку телефон повідомлення "Overflow".

Лістинг 2. Компіляція і запуск коду з лістингу 1

$ Xlc -o ex1 ./example01.c $ ./ex1 Sum is 9 // Результат складання 11 і - 2 Overflow // В результаті складання змінних a = 0x7fffffff і b = 0x7fffffff виникло переповнення

Повна інформація про всіх значеннях коду умов, які можуть бути встановлені різними інструкціями, приведена в документації до всієї системи. Зокрема, ця інформація міститься в додатку Appendix C. Condition-Code Settings книги z / Architecture Principles of Operation (EN) (документ IBM SA22-7832-10).

асемблерні інструкції

Основні інструкції працюють з даними, що зберігаються в регістрах загального призначення. Інші інструкції працюють з даними слова стану програми (це можуть бути дані годин істинного часу або дані, що надходять з потоку команд). Імена деяких інструкцій включають в себе букви, що позначають розрядність операндів, наприклад, H (halfword - півслова) для 16-розрядних, F (word - слово) - для 32-розрядних і G (doubleword - подвійне слово) - для 64-розрядних операндів . Якщо інструкція працює з 32-розрядними операндами, то вона може містити (не обов'язково) в своєму імені букву F. Якщо інструкція з тим же ім'ям працює з 64-розрядними операндами, то її ім'я буде містити букву G. Інструкція з тим же ім'ям, працює з 64-розрядних першим і 32-розрядних другим операндами, буде містити в імені букви GF. За повною інформацією про всі інструкціях звертайтеся до документації вашої системи.

Для спрощення розгляду матеріалу все асемблерні інструкції в цій статті розділені на кілька узагальнених груп. Ми розглянемо тільки деякі основні інструкції, що використовуються розробниками найчастіше. Посилання на повний список ассемблерних інструкцій приведена в розділі ресурси .

Група алгебраїчних арифметичних інструкцій

У цю групу входять інструкції, що виконують алгебраїчні дії з операндами. Операнди арифметичних операцій можуть являти собою єдиний реєстр, адреса пам'яті або безпосереднє значення. У випадках з двома операндами операція застосовується до обох з них, але результат зберігається в першому операнде. Відповідно, над першим операндом виконуються як операції читання (READ), так і операції записи (WRITE).

Для багатьох алгебраїчних арифметичних інструкцій код умови набуває таких значень:

0Результат складання дорівнює нулю, переповнення немає.1Результат менше нуля, переповнення немає.2Результат більше нуля, переповнення немає.3Виникло переповнення.

Перейдемо до розгляду найбільш часто використовуваних форматів.

OP R 1, R 2: формат регістр-регістр
Значення, що зберігається в регістрі, зазначеному за допомогою другого операнда (R2), використовується в таких операціях як додавання, віднімання, множення або ділення, разом із значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1). Результат операції зберігається в першому операнде R1.
R1 ← R1 OP R2

OP R 1, I 2: формат регістр-значення
Безпосереднє значення, вказане за допомогою другого операнда (I2), використовується в операціях разом зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1). Результат операції зберігається в першому операнде R1.
R1 ← R1 OP I2

OP R 1, D 2 (X 2, B 2): формат регістр-адреса
Значення, що зберігається за адресою, вказаною за допомогою другого операнда, використовується в операціях разом зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1). Результат операції зберігається в першому операнде R1.
R1 ← R1 OP [значення-яке зберігається-по-фактичному-адресою D 2 (X 2, B 2)]

Фактична адреса в другому операнде обчислюється таким чином:

D2: зсув щодо базової адреси + B2: базова адреса + X2: покажчик на базовий адреса

Приклади алгебраїчних арифметичних інструкцій
Асемблерні інструкції можуть міститися або в додатках, написаних безпосередньо на асемблері, або в додатках, написаних на мові C / C ++ з використанням вбудованого асемблера. У цьому розділі ми покажемо, як помістити алгебраїчну арифметичну інструкцію AH (Add Halfword) в код програми, що розробляється на мові C. Інструкція AH алгебраїчно складає значення двухбайтового поля (півслова), що зберігається за адресою з другого операнда, зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда. Результат поміщається в регістр першого операнда. У лістингу 3 наведено фрагмент коду на мові C, що передує інструкції asm.

Лістинг 3. Модуль example02.c - вихідний код на мові C без асемблерних інструкцій

1 #include <stdio.h> 2 int main () {3 int a = 0; 4 int ar [] = {0x00112233, 0x44556677}; 5 printf ( "a = 0x% 08x, ar [0] = 0x% 08x, ar [1] = 0x% 08x \ n", a, ar [0], ar [1]); 6 return 0; 7}

При запуску модуля example02.c на екран виводяться значення операндів a, ar [0] і ar [1], як продемонстровано в лістингу 4.

Лістинг 4. Компіляція і запуск модуля example02.c

$ Xlc -o ex2 ./example02.c $ ./ex2 a = 0x00000000, ar [0] = 0x00112233, ar [1] = 0x44556677

Мета даного прикладу полягає в додаванні в код інструкції AH, яка підсумовує двухбайтовое значення, що зберігається за адресою з регістра ar, і значення змінної a. Двухбайтовое значення, що зберігається за адресою в ar - це 0x0011, а початкове значення змінної a - 0x0, отже, підсумковим значенням змінної a після виконання операції буде 0x11.

(1) Додавання інструкції AH в асемблерний код вручну
Файл з асемблерним кодом example02.s отриманий в результаті компіляції модуля example02.c з опцією -S.
$ Xlc example02.c -c -S

Найбільш важлива частина ассемблерного коду з файлу example02.s представлена в лістингу 5.

Лістинг 5. Вихідний асемблерний код модуля example02.s без додаткових інструкцій

.L_main: 1 STMG% r13,% r15,104 (% r15) 2 LARL% r13, $ CONSTANT_AREA 3 LAY% r15, -184 (,% r15) # Резервуємо 184 байта в стеку; r15 містить вершину стека 4 MVHI 176 (% r15), 0 # Розміщуємо 0 (значення змінної a) в байти 176 → 179 регістра r15 5 MVHHI 168 (% r15), 17 # Байти 168 → 169 регістра r15 містять значення 0x00 і 0x11 ( т. е. 1710) 6 MVHHI 170 (% r15), 8755 # Байти 170 → 171 регістра r15 містять значення 0x22 і 0x33 (т. е. 875 510) 7 MVHHI 172 (% r15), 17493 # Байти 172 → 173 регістра r15 містять значення 0x44 і 0x55 (т. е. 1749310) 8 MVHHI 174 (% r15), 26231 # Байти 174 → 175 регістра r15 містять значення 0x66 і 0x77 (т. е. 2623110) LA% r2,16 (,% r13) # Завантаження параметрів для функції printf LGF% r3,176 (,% r15) LGF% r4,168 (,% r15) LGF% r5,172 (,% r15) BRASL% r14, printf # Виклик функції printf

У рядках 4-8 виконується заповнення стека значеннями операндів a (містить 0), ar [0] (містить 0x00112233) і ar [1] (містить 0x44556677).

На малюнку 2 показано вміст стека після виконання команди з рядка 8. Починаючи з рядка 9 виконується підготовка параметрів, переданих у функцію printf, яка потім викликається за допомогою інструкції BRASL.

Малюнок 2. Вміст стека після виконання команди з рядка 8

Щоб добитися необхідних результатів, в файл example02.s необхідно додати інструкцію AH (в форматі регістр-адреса), помістивши її перед блоком підготовки параметрів функції printf, як показано в лістингу 6.

Лістинг 6. Додавання ассемблерних інструкцій в код модуля example02.s

.L_main: 1 STMG% r13,% r15,104 (% r15) 2 LARL% r13, $ CONSTANT_AREA 3 LAY% r15, -184 (,% r15) # Резервуємо 184 байта в стеку; r15 містить вершину стека 4 MVHI 176 (% r15), 0 # Розміщуємо 0 (значення змінної a) в байти 176 → 179 регістра r15 5 MVHHI 168 (% r15), 17 # Байти 168 → 169 регістра r15 містять значення 0x00 і 0x11 ( т. е. 1710) 6 MVHHI 170 (% r15), 8755 # Байти 168 → 169 регістра r15 містять значення 0x00 і 0x11 (т. е. 875 510) 7 MVHHI 172 (% r15), 17493 # Байти 172 → 173 регістра r15 містять значення 0x44 і 0x55 (т. е. 1749310) 8 MVHHI 174 (% r15), 26231 # Байти 174 → 175 регістра r15 містять значення 0x66 і 0x77 (т. е. 2623110) 9 LA% r1,168 (,% r15 ) # Завантажуємо адреса масиву ar в стеці в регістр r1 10 L% r0,176 (,% r15) # Завантажуємо значення змінної a (байти 176 → 179 регістра r15) в регістр r0 11 AH% r0, 0 (% r1) # Отримуємо дво хбайтовое значення зі зсувом 0 щодо адреси масиву ar (реєстр r1) і поміщаємо результат в регістр r0 12 ST% r0,176 (,% r15) # Результат виконання інструкції AH завантажується з регістра r0 назад в змінну a (байти 176 → 179 регістра r15 ) LA% r2,16 (,% r13) # Завантаження параметрів для функції printf LGF% r3,176 (,% r15) LGF% r4,168 (,% r15) LGF% r5,172 (,% r15) BRASL% r14 , printf # Виклик функції printf

Інструкція AH повинна знати адресу масиву ar, щоб прочитати два байта з цього місця розташування. Також їй потрібно знати поточне значення змінної a, щоб виконати операцію додавання. Щоб підготувати необхідну інформацію для інструкції AH, були додані рядки 9 і 10.

  • Рядок 9: інструкція LA завантажує адресу масиву ar в регістр r1.
  • Рядок 10: інструкція L завантажує поточне значення змінної a в регістр r0.
  • Рядок 11: регістри r0 і r1 використовуються інструкцією AH.

У рядку 11 інструкція AH виконує наступні операції:

  1. Отримання двухбайтового значення, що зберігається із зсувом 0 щодо адреси масиву ar.
    Оскільки ar = {0x00112233, 0x44556677}, два байта із зсувом 0 являють собою значення 0x0011.
  2. Додавання отриманого двухбайтового значення до вмісту регістра r0.
    Оскільки на поточний момент регістр r0 містить значення змінної a, рівне 0, то сумарне значення буде 0x11.
  3. Збереження результатів в регістрі r0: тепер регістр r0 містить нове значення, 0x11.

Після виконання інструкції AH в рядку 11 регістр r0 містить нове значення. Інструкція ST в рядку 12 зберігає результат в змінну a.

Щоб оцінити результат додавання інструкції AH і інших ассемблерних інструкцій (рядки 9-12), скомпілюємо і запустимо на виконання оновлений асемблерний код.

Лістинг 7. Компіляція і запуск оновленого ассемблерного коду

$ Xlc -o ex2_new ./example02.s $ ./ex2_new a = 0x00000011, ar [0] = 0x00112233, ar [1] = 0x44556677 → a має значення 0x11, як і очікувалося

(2) Додавання інструкції AH в код C за допомогою вбудованого асемблера
Вбудований асемблер дозволяє отримати той же самий результат з істотно меншими зусиллями.

Лістинг 8. Модуль example02.c - додавання оператора вбудованого асемблера в код C

1 #include <stdio.h> 2 int main () {3 int a = 0; 4 int ar [] = {0x00112233, 0x44556677}; 5 asm ( "AH% 0,% 1": "+ r" (a): "m" (ar [0])); // тут додана інструкція AH 6 printf ( "a = 0x% 08x, ar [0] = 0x% 08x, ar [1] = 0x% 08x \ n", a, ar [0], ar [1]); 7 return 0; 8}

Все, що необхідно зробити при використанні вбудованого асемблера - це додати в код основну інструкцію (в нашому випадку це інструкція AH). Про всі допоміжних операціях (наприклад, LOAD ADDRESS, LOAD і STORE в рядках 9, 10 і 12) подбає компілятор. Використання вбудованого асемблера в коді C має привести до тих же самим результатам, що і в попередньому прикладі.

Лістинг 9. Компіляція і запуск оновленого коду C з оператором вбудованого асемблера

$ Xlc -o ex2_asm ./example02_asm.c $ ./ex2_asm a = 0x00000011, ar [0] = 0x00112233, ar [1] = 0x44556677 → a має значення 0x11, як і очікувалося

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

Група інструкцій порівняння

Інструкції цієї групи порівнюють два операнда між собою. Залежно від результату порівняння код умови набуває таких значень:

0Два операнда рівні1Першийоперанд менше другого2Першийоперанд більше другого3Не застосовується

COMP R 1, R 2: формат регістр-регістр
Значення, що зберігається в регістрі, зазначеному за допомогою другого операнда (R2), порівнюється зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1).

COMP R 1, I 2: формат регістр-значення
Безпосереднє значення, вказане за допомогою другого операнда (2), порівнюється зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1).

COMP R 1, D 2 (X 2, B 2): формат РЕГІСТР-адреси
Значення, що зберігається за адресою, вказаною за допомогою другого операнда, порівнюється зі значенням, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1).

COMP D 1 (B 1), I 2: формат адрес-значення
Безпосереднє значення, вказане за допомогою другого операнда (I2), порівнюється зі значенням, що зберігається за адресою, вказаною за допомогою першого операнда.

Приклади інструкцій порівняння
CH 5, 0x30 (0, 9)
Інструкція CH (Compare Halfword) порівнює 16-розрядний ціле число зі знаком (півслова), що зберігається в пам'яті, з вмістом регістра. Інструкція CH приймає перший операнд як 32-розрядний ціле число зі знаком; зміщення розглядається як 12-розрядний ціле число без знака.

Вихідні дані цього прикладу:

Таблиця 2. Значення регістрів і комірок пам'яті до виклику інструкції CH

Символ в коді операції
OP R1, D2 (X2, B2) ассемблерного формат
CH 5, 0x30 (0, 9) Значення Вміст
OP
R1
D2
X2
B2 CH
5
0x30
0
9 Compare Halfword - порівняння півслова
регістр 5
зсув
індекс 0
регістр 9
0xFFFF 9000 = - 2867210
0x30
0x0
0x00012050 Осередок пам'яті 12080 - 12081 0x9000 = - 2867210

Ефективний адреса: B2 + X2 + D2 = 0x00012050 + 0x0 + 0x30 = 0x00012080.
Значення двох байтів, що зберігаються в комірці пам'яті 12080: 0x9000, або - 2867210.
Вміст регістра 5: 0xFFFF 9000, або - 2867210.

Оскільки ці два значення рівні, код умови містить 0.

Замість безпосереднього обчислення ефективної адреси, як це показано вище, у вбудованому асемблері використовуються адресні обмежувачі, істотно спрощують процес.

Група інструкцій умовного розгалуження

Інструкції умовного розгалуження перевіряють код умови в поточному слові стану програми. Якщо код умови має одне із заданих маскою значень, то поле "адреса наступної інструкції" в слові стану замінюється адресою переходу, зазначеним в інструкції розгалуження. В іншому випадку виконується звичайна черговість команд.

Інструкції умовного розгалуження не змінюють значення коду умови.

BC M 1, R 2: формат регістр-регістр
Якщо код умови має одне зі значень, що визначаються маскою M1, управління передається за адресою, що зберігається в регістрі, зазначеному за допомогою другого операнда (R2).

BC M 1, D 2 (X 2, B 2): формат регістр-пам'ять
Якщо код умови має одне зі значень, що визначаються маскою M1, управління передається за адресою, обчислюваному на основі D 2, X 2 і B 2.

маска
Операнд M являє собою 4-розрядну маску. Чотири значення коду умови (0, 1, 2 і 3) зіставляються зі значеннями розрядів (бітів) цієї маски наступним чином:

Таблиця 3. Зіставлення значень коду умови і розрядів маски

2-розрядний код умови Біти маски Значення маски 002, або 010 1 0 0 0 0x8 012, або 110 0 1 0 0 0x4 102, або 210 0 0 1 0 0x2 112, або 310 0 0 0 1 0x1

Вибір відповідного біта маски проводиться на підставі значення коду умови. Якщо біт маски, обраний виходячи з коду умови, дорівнює одиниці, то умовний перехід виконується, в іншому випадку виконується звичайна черговість команд. Як випливає з лістингу 1, для виконання переходу в разі, коли код умови не дорівнює 3 (т. Е. Дорівнює одному зі значень {0, 1, 2}), маска повинні мати значення 11102, або 0xE або в шістнадцятковій системі.

Якщо всі чотири біта маски або операнд R2 дорівнюють нулю, інструкція розгалуження еквівалентна порожній команді. Аналогічно, якщо маска має значення 11112, виконується безумовний перехід (поки R2 не стане дорівнює нулю). Таким чином, виконання інструкції BCR 15, 0 може істотно знизити продуктивність програми.

Приклади інструкцій умовного розгалуження

Вже згадана нижче функція absoluteValue приймає в якості аргументу ціле число і повертає його абсолютне значення. Цю функцію можна записати в такий спосіб:

int absoluteValue (int a) {return a <0? -A: a; }

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

Лістинг 10. Оператори вбудованого асемблера, що містять інструкції порівняння і умовного розгалуження

1 int absoluteValue (int a) {2 asm ( "CFI% 0, 0 \ n" // Порівнюємо значення змінної a з 0 3 "BRC 0xA, DONE \ n" // Якщо a> = 0, переходимо до мітці DONE ( т. е. пропускаємо рядок 4) 4 "LCR% 0,% 0 \ n" // Якщо потрапили сюди, значить, a <0, і ми застосовуємо двоичное доповнення до змінної a, щоб змінити її знак 5 "DONE: \ n "6:" + r "(a) 7); 8 return a; 9}

Параметр% 0 посилається на перший операнд оператора вбудованого асемблера, змінну a.

У рядку 2 значення змінної a порівнюється з нулем. згідно з розділом Група інструкцій порівняння , Інструкція CFI встановлює значення коду умови в слові стану програми наступним чином.

Таблиця 4. Зіставлення значень коду умови і значень маски

Порівняння a з 0 Код умови Маска a = 0 0 = 002 1000 a <0 1 = 012 0100 a> 0 2 = 102 0010

Згідно з логікою функції absoluteValue (int a), вона повертає значення змінної a, якщо воно більше або дорівнює 0. Це рівноцінно установці значення коду умови в 0 або 2. Маска, що відповідає цим значенням, буде дорівнює 1010, або 0xA в шістнадцятковій системі.

Інструкція BRC в рядку 3 перевіряє, чи відповідає поточне значення коду умови масці 0xA. Якщо ця умова виконується, управління передається по мітці DONE в рядку 5, і функція готова повернути початкове значення змінної a без будь-яких модифікацій. На цьому шляху виконання функція absoluteValue (int a) повертає початкове значення змінної a за умови a> = 0.

Якщо інструкція BRC в рядку 3 не знаходить збіги, то виконується інструкція LCR (Load Complement) в рядку 4, яка завантажує двоичное доповнення другого операнда (змінної a) за адресою першого операнда (змінна a), змінюючи таким чином знак змінної a. На цьому шляху виконання функція absoluteValue (int a) повертає значення - a при негативному значенні змінної a.

Для досягнення максимальної продуктивності своїх додатків розробники ПЗ можуть вибирати оптимальні інструкції з величезного набору ассемблерних інструкцій, доступних для платформи z Systems. Наприклад, для підвищення продуктивності попередню функцію можна написати по-іншому (див. лістинг 12 ).

Група інструкцій завантаження

Інструкції завантаження (Load) поміщають другий операнд (в початковому вигляді або доповнене знаком) на місце першого операнда.
Операнди мають таке спрямування: Операнд 1 Операнд 2.

LR 1, R 2: формат регістр-регістр
Значення, що зберігається в регістрі, зазначеному за допомогою другого операнда (R2), завантажується (в початковому вигляді або доповнене знаком) в регістр, вказаний за допомогою першого операнда (R1).

LR 1, I 2: формат регістр-значення
Безпосереднє значення, вказане за допомогою другого операнда (I2), завантажується (в початковому вигляді або доповнене знаком) в регістр, вказаний за допомогою першого операнда (R1).

LR 1, D 2 (X 2, B 2): формат регістр-адреса
Значення, що зберігається за адресою, вказаною за допомогою другого операнда, завантажується (в початковому вигляді або доповнене знаком) в регістр, вказаний за допомогою першого операнда (R1).

Приклади інструкцій завантаження
Розглянемо модифіковану функцію absoluteValue з розділу Приклади інструкцій умовного розгалуження , Щоб продемонструвати використання інструкцій завантаження.

Лістинг 11. Приклад інструкції завантаження

1 int absoluteValue (int a) {2 asm ( "LTR% 0,% 0 \ n" // Завантаження та перевірка значення змінної a 3 "BRC 0xA, DONE \ n" // Якщо a> = 0, переходимо по мітці DONE (т. е. пропускаємо рядок 4) 4 "LCR% 0,% 0 \ n" // Якщо ми потрапили сюди, значить, a <0, і ми застосовуємо двоичное доповнення до змінної a, змінюючи її знак 5 "DONE: \ n "6:" + r "(a) 7); 8 return a; 9}

У рядку 2 інструкція LTR виконує дві операції:

  1. Завантажує початкове значення другого операнда за адресою першого операнда.
  2. Оновлює значення коду умови на підставі завантаженого значення.
0Звантажену значення дорівнює нулю1Звантажену значення менше нуля2Звантажену значення більше нуля3Не застосовується

Оскільки обидва операнда є один і той же регістр, який зберігає значення змінної a, то використання інструкції LTR в рядку 2 еквівалентно порівнянні значення a з 0 без переміщення даних.

Інструкція BRC в рядку 3 перевіряє, чи відповідає поточне значення коду умови масці 0xA. Якщо ця умова виконується (т. Е. A> = 0), управління передається по мітці DONE в рядку 5, і функція повертає початкове значення змінної a без будь-яких модифікацій. В іншому випадку (a <0) виконується інструкція LCR в рядку 4, яка завантажує двоичное доповнення другого операнда (змінної a), змінюючи знак змінної a, і функція повертає значення - a.

Для підвищення продуктивності можна переписати цю ж функцію за допомогою інших ассемблерних інструкцій. У наступному прикладі єдина інструкція завантаження замінює собою дві попередні інструкції завантаження (LOAD), одну інструкцію розгалуження (BRANCH) і використання мітки.

Лістинг 12. Використання іншого ассемблерной інструкції

1 int absoluteValue (int a) {2 asm ( "LPGFR% 0,% 0 \ n": "+ r" (a)); // Завантаження абсолютного значення змінної a 3 return a; }

Інструкція LPGFR завантажує абсолютне значення другого операнда в перший операнд. Оскільки обидва операнда є змінну a, інструкція фактично виконує присвоювання a = | a | . Після виконання інструкції LPGFR функція повертає очікуване значення.

Група інструкцій збереження

Інструкції збереження (Store, ST) поміщають перший операнд на місце другого операнда.

Операція застосовується до операндам в наступному напрямку: Операнд 1 Операнд 2.

ST R 1, D 2 (X 2, B 2): формат регістр-адреса
Значення, що зберігається в регістрі, зазначеному за допомогою першого операнда (R1), завантажується без змін за адресою, вказаною за допомогою першого операнда

Примітка. Для платформи z Systems існує безліч інших інструкцій. У цій статті розглядаються тільки типові інструкції.

Приклади інструкції збереження
У наступному прикладі інструкція ST зберігає значення змінної a за адресою, що зберігається в змінної b. Оскільки перед початком виконання інструкції a = 1 і b = 0, то після її виконання змінна b повинна дорівнювати 1, а функція повернути 0.

У цьому прикладі використовується адресний обмежувач "m", що суттєво спрощує обчислення адреси.

Лістинг 13. Приклад інструкції збереження

1 int main () {2 int a = 1, b = 0; // b = 0 3 asm ( "ST% 1,% 0 \ n" // Зберігаємо значення змінної a за адресою з змінної b 4: "= m" (b) 5: "r" (a) 6); 7 return a == b? 0: 1; // З умови в рядку 2 слід, що змінна b повинна стати рівною 1. Відповідно функція повинна повернути 0. 8}

Висновок

У загальному випадку напрямок операції інструкції, що працює з двома операндами, залежить від її типу. Інструкції порівняння (COMPARE) і перевірки (TEST) записують результати своєї роботи в код умови. Інструкції розгалуження (BRANCH) перевіряють значення коду умови для визначення точки передачі управління. Інструкції завантаження (LOAD), що працюють з двома операндами, завантажують другий операнд в перший операнд, т. Е. Напрям операції має вигляд Операнд 1Операнд 2. З іншого боку, інструкції збереження (STORE), навпаки, зберігають перший операнд у другому операнд, т. Е. Напрям операції має вигляд Операнд 1Операнд 2. Арифметичні інструкції виконують дії над обома операндами і зберігають результат в першому операнде, т. Е. В цьому випадку напрямок операції має вигляд Операнд 1Операнд 1 OP Операнд 2.

Незважаючи на те, що Linux на платформі z Systems працює з тією ж архітектурою, що і платформи IBM z / OS®, z / TPF, z / VSE® і z / VM®, вбудований асемблер для Linux на платформі z Systems дозволяє працювати тільки з асемблерними інструкціями. Інструкції та макроси високорівневого асемблера (HLASM), наприклад DS, DD або GETMAIN, використовувати не можна.

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

Подякою

Автор висловлює подяку Вісду Вокшурі (Visda Vokhshoori), керівника напрямку z / OS групи по оптимізації компіляторів лабораторії Toronto Software Lab, IBM Canada, за допомогу в роботі над цією статтею.

ресурси

ПОСИЛАННЯ

Ресурси для скачування

Підпішіть мене на ПОВІДОМЛЕННЯ до коментарів