Трек x86-64 на Exercism використовує NASM (The Netwide Assembler) як асемблер.
У NASM коментарі починаються з крапки з комою (;).
Коментарі можна розміщувати будь-де в програмі, а все, що йде після ;, асемблер ігнорує.
Константу часу асемблювання можна визначити в NASM за допомогою equ.
Наприклад, ось так визначається константа з назвою UNIVERSE і значенням 42:
UNIVERSE equ 42
Константи обчислюються один раз, під час визначення, і не можуть бути перевизначені пізніше.
В асемблері замість змінних для зберігання значень ми зазвичай використовуємо регістри. Регістр процесора - це ділянка швидкої памʼяті всередині процесора компʼютера. Більшість обчислень виконується в регістрах процесора.
Частину регістрів використовують для найрізноманітніших обчислень, і їх називають регістрами загального призначення (GPRs). Інші мають спеціальне або вузьке призначення.
У x86-64 є 16 64-бітних регістрів загального призначення (GPRs), до яких можна також звертатися як до 32-бітних, 16-бітних або 8-бітних.
Регістри загального призначення описано нижче, де n у rn набуває значень від 8 до 15: r8, r9, r10, r11, r12, r13, r14 та r15.
| 64-біт | 32-біт | 16-біт | 8-біт |
|---|---|---|---|
| rax | eax | ax | ah/al |
| rbx | ebx | bx | bh/bl |
| rcx | ecx | cx | ch/cl |
| rdx | edx | dx | dh/dl |
| rsi | esi | si | sil |
| rdi | edi | di | dil |
| rbp | ebp | bp | bpl |
| rsp | esp | sp | spl |
| rn | rnd | rnw | rnb |
Коли використовується менш ніж 64 біти, доступ зазвичай іде до молодшої частини регістра.
Виняток із цього правила - ah, bh, ch і dh, які дають доступ до старших 8 бітів із 16-бітної частини регістра.
Ілюстрація того, як звертаються до бітів регістра rax:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Деякі з цих регістрів потрібно зберігати під час викликів функцій: rbp, rsp, rbx, r12, r13, r14 та r15.
Якщо їх не зберегти, це може призвести до помилки або до невизначеної поведінки.
Решту зберігати не потрібно, і їх можна вільно використовувати: rax, rcx, rdx, rdi, rsi, r8, r9, r10 та r11.
Регістри зазвичай зберігають у стеку за допомогою інструкції push.
А потім відновлюють перед завершенням функції за допомогою інструкції pop.
Інструкції - це елементарні обчислення, які вміє виконувати процесор. Зазвичай вони мають такий вигляд:
name destination, source
Отже, спершу йде назва інструкції, потім принаймні один пробільний символ, далі операнд призначення, кома (,) і, нарешті, операнд-джерело.
Інструкція зазвичай не змінює операнд-джерело, лише операнд призначення.
Обидва операнди повинні мати однаковий розмір, якщо не зазначено інше. Наприклад, якщо операнд-джерело має 16 бітів, то операнд призначення теж повинен мати 16 бітів.
Наприклад, щоб зберегти значення в регістрі, можна скористатися інструкцією mov:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
Наведений фрагмент копіює вміст усіх 64 бітів rdx (операнда-джерела) в усі 64 біти rax, який виступає операндом призначення для цієї інструкції.
У більшості інструкцій як операнд-джерело можна використовувати константні цілі числа (але не як операнд призначення):
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Зауважмо, що запис у 32-бітний регістр також очищає старші біти, тому mov eax, 42 рівнозначне mov rax, 42.
Це не стосується 16-бітних і 8-бітних регістрів.
Для арифметичних операцій додавання, віднімання та множення можна використовувати інструкції add, sub і imul:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Інструкції організовано у функції. Усі функції розміщуються в section .text вихідного файлу.
Оголошення функції складається з:
:.Існують спеціальні угоди про те, як аргументи передаються у функцію та повертаються з неї. Дві основні угоди про виклик - System V AMD64 ABI і Microsoft x64.
Цей трек використовує угоду про виклик System V AMD64 ABI, і перші шість цілочисельних аргументів передаються у функцію в регістрах.
Вони передаються в такому порядку: rdi, rsi, rdx, rcx, r8 і r9.
Ціле значення функція повертає в регістрі rax.
Щоб викликати функцію, використовуємо інструкцію call.
Функцію можна викликати будь-де в тому самому вихідному файлі.
Щоб зробити функцію видимою для інших вихідних файлів, потрібно використати директиву global.
Наприклад, ось так оголошується функція sum:
section .text ; functions are placed here
global sum ; sum is visible to other source files
sum:
; first argument is passed in rdi
; second argument is passed in rsi
; return value is placed in rax
mov rax, rdi ; rax is now equal to rdi
add rax, rsi ; rax = rax + rsi
ret ; function return
А це викликає нашу функцію sum з аргументами 3 і 5:
mov rdi, 3 ; First argument in rdi
mov rsi, 5 ; Second argument in rsi
call sum
; The rax register now contains the value 8 (3 + 5), after sum returns