The x86-64 track in Exercism uses NASM (The Netwide Assembler) as its assembler.
In NASM, comments are prefixed by a semicolon (;).
Comments may be placed anywhere in the program and everything that comes after a ; is ignored by the assembler.
An assembler-time constant can be defined in NASM using equ.
For instance, this defines a constant named UNIVERSE with the value 42:
UNIVERSE equ 42
Constants are evaluated once, when defined, and can not be redefined later.
In assembly, instead of variables, we typically use registers to store values. A CPU Register is a piece of fast memory inside a computer's processor. Most computations are carried out in the CPU's registers.
Some of the registers are used for a variety of different computations and are called General Purpose Registers (GPRs). Others have special or dedicated purposes.
In x86-64, there are 16 64-bit General Purpose Registers (GPRs), which can also be accessed as 32-bit, 16-bit, or 8-bit.
The GPRs are described below, where n in rn ranges from 8 to 15: r8, r9, r10, r11, r12, r13, r14 and 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 |
When using less than 64-bits, the bits accessed are usually from the lower portion of the register.
The exception to this rule are ah, bh, ch and dh, which access the upper 8-bits from the 16-bits portion of the register.
Illustration of how the bits are accessed for the rax register:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Some of those registers must be preserved accross function calls: rbp, rsp, rbx, r12, r13, r14 and r15.
Failing to preserve them may lead to an error or to undefined behaviour.
The others are not preserved and may be used freely: rax, rcx, rdx, rdi, rsi, r8, r9, r10 and r11.
Registers are usually preserved on the stack using a push instruction.
They are then restored before the end of the function using a pop instruction.
Instructions are pieces of computations a CPU can perform. They usually have the following form:
name destination, source
So, the name of the instruction is placed first, then at least one whitespace, followed by the destination operand, a comma (,) and finally a source operand.
The source operand isn't typically modified by an instruction, just the destination operand.
Both operands must have the same size unless otherwise noted. For example, the destination operand must also have 16 bits if the source operand has 16 bits.
For instance, to store a value in a register, we can use the mov instruction:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
The snippet above copies the contents of all 64 bits of rdx, the source operand, to all 64 bits of rax, which is the destination operand for the instruction.
It is possible to use constant integer numbers as source operands (but not as destination operands) in most instructions:
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Note that writing to a 32-bit register also clears the upper bits, so mov eax, 42 is the same as mov rax, 42.
This is not true for 16-bit and 8-bit registers.
For the arithmetic operations addition, subtraction, and multiplication, we can use the add, sub, and imul instructions:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Instructions are organized in functions. All functions are placed in the section .text of the source file.
A function declaration consists of:
:.There are specific conventions for how arguments are passed and returned from a function. The two main calling conventions are the System V AMD64 ABI and the Microsoft x64.
This track uses the System V AMD64 ABI calling convention and the six first integers arguments are passed to a function in registers.
They are passed in the following order: rdi, rsi, rdx, rcx, r8, and r9.
An integer value is returned from the function in the rax register.
To call a function, we use the call instruction.
A function can be called anywhere in the same source file.
In order to make a function visible to other source files, the global directive must be used.
For instance, this declares a function 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
And this calls our sum function with the arguments 3 and 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