ALB NASM backend
The ALB NASM backend is the second assembly-native emitter in AdaLogic BASIC's toolchain: same x86-64 Windows target as 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.
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
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:
%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
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: 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
The file opens in NASM's own voice:
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 doesstruc/endstruc— NASM's native record syntax carries the same runtime layouts:ALB_Fact(hash, arg hash, value, active byte) andALB_GC_Node(alive, refs, two children)- Sections —
.text,.data,.bssas 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 the FASM page.
The build is the interesting part
--build on NASM is a three-tool pipeline, and each step has a story:
nasm.exe -f win64— assembles to.obj(COFF)- The CRT trampoline — albnasm writes a tiny C file,
alb_crt_main.c, containingint main(void) { start(); return 0; }, and compiles it. Why? The source documents the scar: "-nostdlib + customstartentry crashes inside Wicked/msvcrt bring-up (ntdll AV)" — going straight intostartskipped runtime initialization that msvcrt needed, so a four-line C shim ownsmainand calls ALB'sstartafter the CRT wakes up. - gcc link —
-Wl,--image-base,0x400000with 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
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
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.
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