Il track x86-64 in Exercism usa NASM (The Netwide Assembler) come assembler.
In NASM, i commenti sono preceduti da un punto e virgola (;).
I commenti possono essere inseriti ovunque nel programma e tutto ciò che segue un ; viene ignorato dall'assembler.
Una costante valutata al momento dell'assemblaggio può essere definita in NASM usando equ.
Ad esempio, questo definisce una costante chiamata UNIVERSE con il valore 42:
UNIVERSE equ 42
Le costanti vengono valutate una sola volta, al momento della definizione, e non possono essere ridefinite in seguito.
In assembly, al posto delle variabili, di solito usiamo i registri per memorizzare i valori. Un registro della CPU è una porzione di memoria veloce all'interno del processore di un computer. La maggior parte dei calcoli viene eseguita nei registri della CPU.
Alcuni registri sono usati per una varietà di calcoli diversi e sono chiamati registri di uso generale (GPR). Altri hanno scopi speciali o dedicati.
In x86-64, ci sono 16 registri di uso generale (GPR) a 64 bit, a cui si può accedere anche come 32 bit, 16 bit o 8 bit.
I GPR sono descritti di seguito, dove n in rn va da 8 a 15: r8, r9, r10, r11, r12, r13, r14 e r15.
| 64-bit | 32-bit | 16-bit | 8-bit |
|---|---|---|---|
| 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 |
Quando si usa meno di 64 bit, i bit a cui si accede sono di solito dalla parte inferiore del registro.
Fanno eccezione ah, bh, ch e dh, che accedono agli 8 bit superiori della porzione a 16 bit del registro.
Illustrazione di come si accede ai bit per il registro rax:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Alcuni di questi registri devono essere preservati attraverso le chiamate di funzione: rbp, rsp, rbx, r12, r13, r14 e r15.
Non preservarli può portare a un errore o a un comportamento indefinito.
Gli altri non vengono preservati e possono essere usati liberamente: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.
I registri vengono di solito preservati sullo stack usando l'istruzione push.
Vengono poi ripristinati prima della fine della funzione usando l'istruzione pop.
Le istruzioni sono operazioni che una CPU può eseguire. Di solito hanno la seguente forma:
name destination, source
Quindi, il nome dell'istruzione viene messo per primo, poi almeno uno spazio bianco, seguito dall'operando di destinazione, una virgola (,) e infine un operando sorgente.
L'operando sorgente di solito non viene modificato da un'istruzione, solo l'operando di destinazione.
Entrambi gli operandi devono avere la stessa dimensione, salvo diversa indicazione. Ad esempio, l'operando di destinazione deve avere anch'esso 16 bit se l'operando sorgente ha 16 bit.
Ad esempio, per memorizzare un valore in un registro, possiamo usare l'istruzione mov:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
Lo snippet sopra copia il contenuto di tutti i 64 bit di rdx, l'operando sorgente, in tutti i 64 bit di rax, che è l'operando di destinazione per l'istruzione.
È possibile usare numeri interi costanti come operandi sorgente (ma non come operandi di destinazione) nella maggior parte delle istruzioni:
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Nota che scrivere in un registro a 32 bit azzera anche i bit superiori, quindi mov eax, 42 equivale a mov rax, 42.
Questo non vale per i registri a 16 bit e a 8 bit.
Per le operazioni aritmetiche di addizione, sottrazione e moltiplicazione, possiamo usare le istruzioni add, sub e imul:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Le istruzioni sono organizzate in funzioni. Tutte le funzioni sono collocate nella section .text del file sorgente.
La definizione di una funzione consiste in:
:.Ci sono convenzioni specifiche su come gli argomenti vengono passati a una funzione e restituiti da essa. Le due principali convenzioni di chiamata sono la System V AMD64 ABI e la Microsoft x64.
Questo track usa la convenzione di chiamata System V AMD64 ABI e i primi sei argomenti interi vengono passati a una funzione nei registri.
Vengono passati nel seguente ordine: rdi, rsi, rdx, rcx, r8, e r9.
Un valore intero viene restituito dalla funzione nel registro rax.
Per chiamare una funzione, usiamo l'istruzione call.
Una funzione può essere chiamata ovunque nello stesso file sorgente.
Per rendere una funzione visibile ad altri file sorgente, bisogna usare la direttiva global.
Ad esempio, questo definisce una funzione 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
E questo chiama la nostra funzione sum con gli argomenti 3 e 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