| | {{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]] |
| | |