Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
NASM=nasm
GCC=gcc

all: asm_call_c c_call_asm inline_asm

asm_call_c: asm_call_c_main.o asm_call_c_sum.o
$(GCC) asm_call_c_main.o asm_call_c_sum.o -no-pie -o asm_call_c

asm_call_c_main.o: asm_call_c_main.asm
$(NASM) -felf64 asm_call_c_main.asm -o asm_call_c_main.o

asm_call_c_sum.o: asm_call_c_sum.c
$(GCC) -c asm_call_c_sum.c -o asm_call_c_sum.o

c_call_asm: c_call_asm_main.o c_call_asm_sum.o
$(GCC) c_call_asm_main.o c_call_asm_sum.o -no-pie -o c_call_asm

c_call_asm_main.o: c_call_asm_main.c
$(GCC) -c c_call_asm_main.c -o c_call_asm_main.o

c_call_asm_sum.o: c_call_asm_sum.asm
$(NASM) -felf64 c_call_asm_sum.asm -o c_call_asm_sum.o

inline_asm: inline_asm.o
$(GCC) inline_asm.o -o inline_asm

inline_asm.o: inline_asm.c
$(GCC) -masm=intel -c inline_asm.c -o inline_asm.o

clean:
rm -f *.o asm_call_c c_call_asm inline_asm
158 changes: 158 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# C <-> ASM Interface Examples (x86-64, System V AMD64)

This folder contains 3 examples that mirror the style of `func-cdecl`, focused on calling between C and assembly.

## Files

- `asm_call_c_main.asm` + `asm_call_c_sum.c`
- Assembly `main` defines the array in `.data`, calls a C function that sums the array, then prints the result.

- `c_call_asm_main.c` + `c_call_asm_sum.asm`
- C `main` has a globally initialized array and calls an assembly function to compute the sum.

- `inline_asm.c`
- Single C file that uses GCC inline assembly to sum an array.

## Build and run

```bash
make
./asm_call_c
./c_call_asm
./inline_asm
```

## Clean

```bash
make clean
```

---

## GCC Extended Inline Assembly

### Basic structure

```c
__asm__ volatile (
"asm instructions"
: outputs
: inputs
: clobbers
);
```

Each section is separated by `:`. Any section can be empty (just leave it blank or omit trailing colons).

---

### Why `volatile`?

Without `volatile`, GCC treats the asm block like a pure function: if it thinks the outputs are unused or the block can be hoisted/eliminated/reordered, it will do so.

`volatile` suppresses all of that:
- The block is **always emitted**, even if the output is unused.
- The block is **not moved** relative to other memory operations.
- Multiple identical blocks are **not merged** into one.

Use `volatile` whenever the asm has side effects beyond its declared outputs (e.g. it reads/writes memory, modifies flags, or does I/O). Omit it only for pure computational blocks where GCC optimizing away redundant calls is acceptable.

---

### Outputs

```c
: "=&r" (sum)
```

Each output is `"constraint" (c_variable)`. The variable receives the register value after the block.

| Modifier | Meaning |
|---|---|
| `=` | write-only - value on entry is discarded |
| `+` | read-write - value is read on entry and written on exit |
| `&` | early-clobber - this register is written before all inputs are consumed; GCC must not share it with any input |

---

### Inputs

```c
: "r" (arr), "r" (n)
```

Each input is `"constraint" (c_expression)`. GCC loads the value into the chosen location before the block. Inputs are numbered after outputs: if there is one output (`%0`), inputs start at `%1`, `%2`, ...

---

### Operand constraints

| Constraint | Location |
|---|---|
| `r` | any general-purpose register (GCC chooses) |
| `a` | `rax` / `eax` |
| `b` | `rbx` / `ebx` |
| `c` | `rcx` / `ecx` |
| `d` | `rdx` / `edx` |
| `S` | `rsi` / `esi` |
| `D` | `rdi` / `edi` |
| `m` | memory operand |
| `i` | immediate integer constant |

With `r`, GCC picks the register - use size modifiers in the asm string to get the right-width name:

| Modifier | Register size | Example (`%0` → `rsi`) |
|---|---|---|
| `%q0` | 64-bit | `rsi` |
| `%k0` | 32-bit | `esi` |
| `%w0` | 16-bit | `si` |
| `%b0` | 8-bit | `sil` |

With specific constraints (`a`, `c`, `S`, ...) you know the register name and can write it directly.

---

### Named operands

Instead of positional `%0`, `%1`, you can use names:

```c
__asm__ volatile (
"add %k[sum], dword ptr [%q[arr] + r8 * 4]"
: [sum] "=&r" (sum)
: [arr] "r" (arr), [n] "r" (n)
: "r8", "cc", "memory"
);
```

---

### Clobbers

Declare every register and resource the asm modifies that is not an output:

| Clobber | Meaning |
|---|---|
| `"rax"`, `"r8"`, ... | asm modifies this register |
| `"cc"` | asm modifies RFLAGS (`add`, `sub`, `cmp`, `test`, `inc`, `dec`, ...) |
| `"memory"` | asm reads/writes memory GCC cannot see; acts as a compiler barrier - GCC flushes cached values and cannot reorder memory ops across the block |

Omitting a clobber is undefined behaviour: GCC may place a live value in that register and your asm will silently corrupt it.

---

### Numeric labels

Use numbers instead of named labels to avoid collisions when the same asm block is inlined multiple times:

```c
"test ecx, ecx\n\t"
"jle 2f\n\t" // jump forward to label 2
"1:\n\t" // loop top
" ...\n\t"
"jl 1b\n\t" // jump backward to label 1
"2:\n\t" // exit
```

`f` = forward, `b` = backward. The same number can appear many times; the direction disambiguates which instance is the target.
26 changes: 26 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_main.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
section .data
values: dd 7, -2, 10, 5, 4
values_len: equ ($ - values) / 4
fmt_print: db "asm_call_c sum = %d", 10, 0

section .text
global main
extern sum_array_c
extern printf

main:
push rbp
mov rbp, rsp

lea rdi, [rel values]
mov esi, values_len
call sum_array_c

mov esi, eax
lea rdi, [rel fmt_print]
xor eax, eax
call printf

xor eax, eax
leave
ret
12 changes: 12 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_sum.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <stddef.h>

int sum_array_c(const int *arr, int n)
{
int sum = 0;

for (int i = 0; i < n; i++)
sum += arr[i];

return sum;
}
19 changes: 19 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <stdio.h>

#include "c_call_asm_sum.h"

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif

int g_values[] = {3, 4, -1, 9, 12, 5};
int g_values_len = (int)ARRAY_SIZE(g_values);

int main(void)
{
int sum = sum_array_asm(g_values, g_values_len);

printf("c_call_asm sum = %d\n", sum);
return 0;
}
20 changes: 20 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
section .text
global sum_array_asm

; int sum_array_asm(const int *arr, int n)
; arr -> RDI, n -> ESI, return -> EAX
sum_array_asm:
xor eax, eax
xor edx, edx

test esi, esi
jle .done

.loop:
add eax, dword [rdi + rdx * 4]
inc edx
cmp edx, esi
jl .loop

.done:
ret
8 changes: 8 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: BSD-3-Clause */

#ifndef C_CALL_ASM_SUM_H
#define C_CALL_ASM_SUM_H

int sum_array_asm(const int *arr, int n);

#endif /* C_CALL_ASM_SUM_H */
41 changes: 41 additions & 0 deletions curs/chap-08-C-asm/bonus-CA-x64/inline_asm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <stdio.h>

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif

static int sum_array_inline(const int *arr, int n)
{
int sum;

/* %0 = sum (output, eax), %1 = arr (pointer), %2 = n (int) */
__asm__ volatile (
"xor %k0, %k0\n\t" /* sum = 0 */
"xor r8d, r8d\n\t" /* index i = 0 */
"test %k2, %k2\n\t" /* if (n <= 0) skip */
"jle 2f\n\t"
"1:\n\t"
"add %k0, dword ptr [%q1 + r8 * 4]\n\t" /* sum += arr[i] */
"inc r8d\n\t" /* i++ */
"cmp r8d, %k2\n\t" /* if (i < n) loop */
"jl 1b\n\t"
"2:\n\t"
: "=&r" (sum)
: "r" (arr), "r" (n)
: "r8", "cc", "memory"
);

return sum;
}

int main(void)
{
int values[] = {8, 1, -3, 20, 4};
int n = (int)ARRAY_SIZE(values);
int sum = sum_array_inline(values, n);

printf("inline_asm sum = %d\n", sum);

return 0;
}