Skip to content
ALB Wiki

Diff — ALB NASM backend

Comparing rev 1 → rev 2

{{infobox
| title = ALB NASM backend
| tool = `albnasm.exe`
| produces = NASM x86-64 assembly (.asm) → .obj → PE64 .exe/.dll
| assembler = [Netwide Assembler](https://www.nasm.us) (`nasm -f win64`)
| linker = gcc/ld — with a generated CRT trampoline
| emitter_unit = `Emit_Native_NASM` (emit_native_nasm.adb)
| status = Alternate-assembly native backend; `--build` supported
}}
The **ALB NASM backend** is the second assembly-native emitter in [[AdaLogic BASIC]]'s toolchain: same x86-64 Windows target as [[ALB FASM backend|FASM]], different assembler. Where FASM emits its own `format PE64` program complete with import tables, NASM emits a **COFF object file** (`nasm -f win64`) that gets linked the traditional way — which makes it the backend you reach for when you want the object, the link step, and everything that exposes.
```alb One-liner
albnasm demo.alb --build
→ nasm.exe -f win64 → demo.obj → gcc link → demo.exe
```
-
## Why a second assembler backend
+
# Why a second assembler backend
FASM assembles and links in one self-contained step — beautiful, but its PE64 emission is monolithic. The NASM route exists for the opposite reason: it produces a **standard `.obj`** you can inspect, feed to any linker, or combine with objects from other languages. It's the same emitter design ported to a different dialect — and porting it surfaced things worth seeing.
-
## Same emitted text, two assemblers
+
# Same emitted text, two assemblers
The NASM emitter inherits the whole FASM program shape — the infix expression style, the `invoke` calling convention, the hardened traps, the runtime library — but NASM doesn't speak FASM's dialect. The solution is a **167-line include file**, `win64nasm.inc`, that reimplements the FASM call dialect inside NASM's preprocessor:
```nasm
%macro invoke 1-*
  %assign %%space 32
  %if %%argc > 4
    %assign %%space (((%%argc + 1) & ~1) * 8)
  %endif
  sub rsp, %%space
  __win64_movarg rcx, %2
  __win64_movarg rdx, %3
  __win64_movarg r8,  %4
  __win64_movarg r9,  %5
  …args 5+ spill to [rsp+32], [rsp+40]…
  call %1
  add rsp, %%space
%endmacro
```
A complete Microsoft x64 calling-convention shim — shadow space, register order, stack spill — written in **preprocessor macros**. It even parses argument text: `addr Symbol` becomes `lea`, and a trailing `d` on a register name (`eax`-style) triggers the zero-extending 32-bit move. The include also declares the whole extern surface (`ExitProcess`, `CreateFileA`, `printf`, `PlaySoundA`, `LoadLibraryA`, `GetProcAddress`…) and stubs out `library`/`import`.
The payoff is architectural: **the emitter can write one source text that assembles under either dialect.** FASM reads `invoke` natively; NASM reads it through this shim. One AST, two assemblers, zero duplicated codegen.
-
## The honest gap
+
# The honest gap
FASM's signature trick — the `ALB_LET` `match`-macro that fuses emitted infix expressions into tight instruction sequences at assemble time — **isn't portable**. NASM's preprocessor can inspect tokens (`%ifidn`, `%substr`) but can't rewrite instruction sequences the way `match` does. The NASM emitter says so itself, in a comment:
```nasm
; NASM: passthrough macro (FASM match/rewrite not ported).
%macro ALB_LET 1+
  %1
%endmacro
```
The macro still exists — emitted source keeps the `ALB_LET` marker so the two dialects share text — but on NASM it's a documented passthrough, not an optimizer. The peephole fusion is a FASM superpower, not a NASM limitation to hide.
-
## What it emits
+
# What it emits
The file opens in NASM's own voice:
```nasm
bits 64
default rel
global start
%include "win64nasm.inc"
```
- **`default rel`** — RIP-relative addressing by default, which modern linkers and ASLR want; FASM's flat model doesn't need it, COFF does
- **`struc`/`endstruc`** — NASM's native record syntax carries the same runtime layouts: `ALB_Fact` (hash, arg hash, value, active byte) and `ALB_GC_Node` (alive, refs, two children)
- **Sections** — `.text`, `.data`, `.bss` as real COFF sections instead of PE-embedded areas
- **`global start`** — the entry is *exported* for the linker to find, rather than declared inside the format header
Beyond the framing, the same hardened runtime is ported wholesale: traps, strings, files, the Prolog knowledge base, temporal variables, reversible blocks, windows and pixels and sound — the feature list tracks [[ALB FASM backend|the FASM page]].
-
## The build is the interesting part
+
# The build is the interesting part
`--build` on NASM is a three-tool pipeline, and each step has a story:
1. **`nasm.exe -f win64`** — assembles to `.obj` (COFF)
2. **The CRT trampoline** — albnasm *writes a tiny C file*, `alb_crt_main.c`, containing `int main(void) { start(); return 0; }`, and compiles it. Why? The source documents the scar: *"-nostdlib + custom `start` entry crashes inside Wicked/msvcrt bring-up (ntdll AV)"* — going straight into `start` skipped runtime initialization that msvcrt needed, so a four-line C shim owns `main` and calls ALB's `start` after the CRT wakes up.
3. **gcc link** — `-Wl,--image-base,0x400000` with **the same 16 MiB stack and heap as the FASM builds** (`-Wl,--stack,16777216 -Wl,--heap,16777216`), linking kernel32/user32/gdi32/winmm/advapi32 plus **SDL3 and ws2_32** — matching the feature surface, not just the frame.
The driver itself — `albnasm.adb`, ~41,000 lines, a full `albt`-scale front end — stages `win64nasm.inc` into the build directory, resolves `nasm.exe`/`gcc.exe` through the toolchain's bundled-tools logic, and emits the same family of `build_*.bat` / `build_shared_*` scripts the C backend stages. `--dll` produces a shared library through the same COFF path.
-
## `--gfx` — the renderer door
+
# `--gfx` — the renderer door
`albnasm` also accepts `--gfx=<backend>` with a real roster — `opengl`, `vulkan`, `d3d6`…`d3d12` — wired to the `alb_gfx_<backend>.dll` runtime. The assembly backend doesn't draw; it *links to the renderer you ask for*, which is exactly what a hardware-abstraction layer is for.
-
## Under the hood
+
# Under the hood
`Emit_Native_NASM` (~7,300 lines, SPARK-annotated) implements the extended `Emit_*` surface — the one shared with C: file suite, `PEEK`/`POKE`, FFI header includes and the loader prelude, the Prolog **cut** operator, draw/fill/blit calls, and **native C blocks** alongside native ASM blocks — polyglot source lowered through the assembly path.
```note The honest summary
NASM is the backend where the design gets *audited*: every cleverness in the FASM path had to earn a NASM translation or admit it couldn't port. `win64nasm.inc` is proof the emitter's Win32 dialect is portable; the `ALB_LET` passthrough is the honest ledger entry of what wasn't.
```
-
## See also
+
# See also
- [[ALB FASM backend]] — the sibling whose dialect it borrows
- [[ALB FASM16 backend]] · [[ALB C backend]] · [[ALB TypeScript backend]]
- [[AdaLogic BASIC]] — the language
- [[History of Programming]] — where the assembler/linker split came from
[[Category:Backends]]
[[Category:AdaLogic BASIC]]