Inline Assembly
Part of: Reef Language Reference Last reviewed on version: 0.8.0
Reef supports inline assembly for low-level hardware access, performance-critical code, and OS/embedded development. Assembly functions are defined at the module level and generate native machine code for the target architecture.
Syntax
Assembly Procedure (No Return Value)
asm proc name(param1: type1, param2: type2) for architecture
// Assembly instructions
end name
Assembly Function (With Return Value)
asm fn name(param1: type1, param2: type2): return_type for architecture
// Assembly instructions
MOV result, value // Use 'result' for return value
end name
Supported Architectures
| Architecture | Syntax Style | Target Flag |
|---|---|---|
amd64 |
Intel syntax | --target amd64 or --target amd64-baremetal |
arm64 |
ARM syntax | --target arm64 or --target arm64-baremetal |
riscv64 |
RISC-V syntax | --target riscv64 or --target riscv64-baremetal |
Basic Examples
x86-64 (AMD64)
// Halt CPU - wait for interrupt
asm proc hlt() for amd64
HLT
end hlt
// Read from I/O port
asm fn inb(port: int): int for amd64
MOV EDX, port
XOR EAX, EAX
IN AL, DX
MOV result, EAX
end inb
// Write to I/O port
asm proc outb(port: int, value: int) for amd64
MOV EDX, port
MOV EAX, value
OUT DX, AL
end outb
// Atomic compare-and-swap
asm fn cas(ptr: pointer, old_val: int, new_val: int): int for amd64
MOV RAX, old_val
MOV RCX, new_val
MOV RDX, ptr
LOCK CMPXCHG [RDX], ECX
MOV result, EAX
end cas
ARM64 (AArch64)
// Wait for interrupt
asm proc wfi() for arm64
WFI
end wfi
// Data memory barrier
asm proc dmb() for arm64
DMB SY
end dmb
// Read system register (CNTPCT_EL0 - timer)
asm fn read_timer(): int for arm64
MRS X0, CNTPCT_EL0
MOV result, X0
end read_timer
// Load-exclusive for atomics
asm fn ldxr(ptr: pointer): int for arm64
LDXR W0, [X0]
MOV result, W0
end ldxr
RISC-V 64-bit
// Full memory fence
asm proc memory_barrier() for riscv64
fence iorw, iorw
end memory_barrier
// No-op for spin-wait loops
asm proc cpu_pause() for riscv64
nop
end cpu_pause
// Add two values
asm fn add_native(a: int, b: int): int for riscv64
add a0, a0, a1
mv result, a0
end add_native
// Read cycle counter (CSR)
asm fn read_cycle(): int for riscv64
rdcycle a0
mv result, a0
end read_cycle
Parameter Access
Parameters are accessed by name within assembly code:
asm fn add_values(a: int, b: int): int for amd64
MOV EAX, a // Load parameter 'a'
ADD EAX, b // Add parameter 'b'
MOV result, EAX // Store in 'result'
end add_values
The compiler substitutes parameter names with appropriate register/memory operands.
Return Values
For asm fn (functions with return values):
- Use
resultas the destination for the return value - The compiler handles moving
resultto the appropriate return register
asm fn get_flags(): int for amd64
PUSHFQ
POP RAX
MOV result, RAX
end get_flags
Register Usage
AMD64 Conventions
- Parameters: Passed in registers (RDI, RSI, RDX, RCX, R8, R9) or stack
- Return: RAX (integer), XMM0 (floating point)
- Callee-saved: RBX, RBP, R12-R15
- Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11
ARM64 Conventions
- Parameters: X0-X7 (integer), D0-D7 (floating point)
- Return: X0 (integer), D0 (floating point)
- Callee-saved: X19-X28, X29 (FP), X30 (LR)
- Caller-saved: X0-X18
Labels
Use local labels with . prefix to avoid conflicts:
asm proc delay_loop(count: int) for amd64
MOV RCX, count
.loop:
DEC RCX
JNZ .loop
end delay_loop
Multi-Architecture Support
Define the same function for multiple architectures:
// AMD64 version
asm proc memory_barrier() for amd64
MFENCE
end memory_barrier
// ARM64 version
asm proc memory_barrier() for arm64
DMB SY
end memory_barrier
The compiler selects the appropriate version based on --target.
Baremetal Compilation
For OS kernels and embedded systems, use baremetal targets:
reefc kernel.reef --target amd64-baremetal --entry none --emit-c
Baremetal Flags
| Flag | Description |
|---|---|
--target amd64-baremetal |
x86-64 freestanding mode |
--target arm64-baremetal |
ARM64 freestanding mode |
--no-stdlib |
Skip libc linkage |
--entry <name> |
Custom entry point name |
--entry none |
Don't generate entry point |
--linker-script <path> |
Custom linker script |
Example: Minimal Kernel
// kernel.reef - Minimal x86-64 kernel
asm proc hlt() for amd64
HLT
end hlt
asm proc cli() for amd64
CLI
end cli
extern "C" proc reef_putchar(c: char)
proc print_string(s: string)
mut i = 0
// Reading s[i] up to and including the NUL terminator requires
// unsafe -- the checked form panics on the index == length read this
// idiom needs (same idiom as core.str.length, which wraps it the same
// way).
unsafe
mut ch = s[i]
while ch != '\0'
reef_putchar(ch)
i = i + 1
ch = s[i]
end while
end unsafe
end print_string
proc main()
cli()
print_string("Hello from Reef OS!")
loop
hlt()
end loop
end main
Compile:
reefc kernel.reef --target amd64-baremetal --entry none --emit-c -o kernel.c
gcc -masm=intel -ffreestanding -nostdlib -c kernel.c -o kernel.o
Use Cases
1. OS Development
- Interrupt handlers
- Context switching
- Page table manipulation
- I/O port access
2. Embedded Systems
- Hardware register access
- Timing-critical loops
- Power management (WFI, HLT)
3. Performance Critical Code
- SIMD operations
- Atomic primitives
- Custom memory operations
4. Hardware Abstraction
- CPU feature detection
- System register access
- Cache control
Restrictions
-
Module Level Only: Assembly functions must be declared at module level, not inside other functions or Active Objects
-
No Active Objects: Cannot define
asm fninside Active Object definitions -
Simple Types: Parameters must be simple types (int, pointer, char, etc.)
-
Single Architecture: Each
asm fn/proctargets one architecture -
No Closures: Assembly functions cannot capture variables
Generated Code
Reef generates GCC-compatible inline assembly. For AMD64, all inline asm uses
Intel syntax (destination-first operand order, bare register names). The
compiler automatically passes -masm=intel to the C compiler (clang or gcc)
so that the assembler interprets instructions correctly.
asm fn add(a: int, b: int): int for amd64
MOV EAX, a
ADD EAX, b
MOV result, EAX
end add
Generates:
int reef_add(int a, int b) {
int __result;
__asm__ __volatile__ (
"MOV EAX, %[a]\n"
"ADD EAX, %[b]\n"
"MOV %[result], EAX\n"
: [result] "=r" (__result)
: [a] "r" (a), [b] "r" (b)
: "memory", "cc", "eax"
);
return __result;
}
The compiler automatically detects hard-coded register names in the asm body
and adds them to the GCC clobber list (e.g., "eax" above). This prevents
the C compiler from assuming those registers are preserved across the asm block.
AMD64 Intel Syntax and -masm=intel
AMD64 inline asm in Reef uses Intel syntax: bare register names (rax, not
%rax), destination-first operand order (MOV dest, src), and no prefix
on memory operands. This matches RISC-V and ARM64 conventions, keeping all
three architectures consistent.
When you compile normally (reefc file.reef), the compiler handles everything
automatically — it detects AMD64 asm blocks and passes -masm=intel to
clang/gcc.
Using --emit-c (manual C compilation)
When using --emit-c to generate C code for manual compilation, you must
pass -masm=intel yourself:
# Generate C code
reefc kernel.reef --target amd64-baremetal --entry none --emit-c
# Compile the C — note the -masm=intel flag!
gcc -masm=intel -ffreestanding -nostdlib -c kernel.c -o kernel.o
# or
clang -masm=intel -ffreestanding -nostdlib -c kernel.c -o kernel.o
Without -masm=intel, the assembler defaults to AT&T syntax and will reject
Intel-syntax instructions with errors like:
error: unknown use of instruction mnemonic without a size suffix
The -masm=intel flag works with both GCC and Clang. It tells the assembler
to interpret inline asm as Intel syntax and to expand GCC asm operand
references (like %[result]) without the AT&T % prefix on registers.
Note: ARM64 and RISC-V are unaffected — those architectures have a single assembly syntax defined by the ISA spec, with bare register names that work naturally with GCC extended asm.
Best Practices
- Keep It Simple: Use inline assembly only when necessary
- Document Intent: Add comments explaining what the assembly does
- Test Thoroughly: Assembly bypasses type checking
- Use Labels: Prefix with
.to avoid symbol conflicts - Consider Portability: Provide versions for each target architecture
- Use Parameter Names: Write
MOV EAX, portnotMOV EAX, EDI— the compiler maps parameters to operands and detects hard-coded registers for the clobber list - Remember
-masm=intel: When using--emit-cfor AMD64, pass-masm=intelto your C compiler
See Also
- FFI (Foreign Function Interface) - Calling C functions
- Unsafe Blocks - Low-level memory operations
examples/baremetal_kernel/in the Reef source tree - complete OS example