code wiki / (root) / nxasm_x86_main.nx

nxasm_x86_main.nx source

↩ module page · 244 lines · 12606 B

1// RECOVERED + ADAPTED into buildroot/runtime/ 2026-08-07 (debts 1786109488 / 1786109910). 2// This driver was ABSENT from the NAS build tree: buildroot/nxasm/ does not exist and no 3// *x86_main*.nx existed under buildroot, so the x86 assembler -- half the sovereign toolchain -- 4// had a LIVE binary and no source to rebuild it from. Sole copy was nxc2/nxasm/ on the laptop. 5// 6// TWO ADAPTATIONS, both forced by consolidations this tree has had since the copy was taken: 7// (1) import "../runtime/nx_syscalls_x86_64.nx" -> "nx_syscalls_x86_64.nx". The relative path 8// only resolves from a sibling dir; buildroot/nxasm/ could not be created (nx_fs_write does 9// not make parents -- silent exit 4 -- and nx_mkdirp is allowlisted to knowledge/|sites/|/tmp/). 10// (2) sys_read_file_x86_64 -> sys_read_file. THIS ONE NEARLY WENT THE WRONG WAY. The symbol is 11// defined NOWHERE in buildroot, and three laptop copies (.build/runtime, .tmp-rv64/inputs, 12// bench/ir_diff_corpus) hold an 11,763 B nx_syscalls_x86_64.nx that DOES define it -- so the 13// obvious read was "buildroot's 1,388 B copy is truncated, restore the big one". 14// THAT READ IS WRONG. buildroot's copy is a DELIBERATE ALIAS STUB (2026-07-31, debt 15// 1785528831) forwarding to nx_syscalls.nx, which now owns the wrapper set once. It exists 16// to FIX a measured defect: 37 translation units reached BOTH layers, held every wrapper 17// twice, and resolved silently by definition order. Restoring the 11,763 B file would have 18// reverted that fix and re-armed the double-definition bug. 19// READING THE FILE'S OWN HEADER IS WHAT STOPPED IT. 20// ★A SMALLER FILE IS NOT EVIDENCE OF TRUNCATION -- IT CAN BE THE RESULT OF A CONSOLIDATION THAT 21// MOVED THE CONTENT SOMEWHERE BETTER. ASK THE FILE BEFORE YOU RESTORE OVER IT. 22// 23// ⚠NOT YET PROVEN AUTHENTIC: a successful build does NOT establish that this is the source the 24// live nxasm_x86_main.elf was built from. buildroot/_build/nxasm_x86_main.s survives from a prior 25// build -- rebuild-and-compare against it (or against the live ELF) is the real authenticity test.// nxasm_x86_main.nx -- CLI front-end for the sovereign x86_64 assembler. 26// 27// Usage: nxasm_x86 <input.s> <output.elf> 28// 29// Reads an nxc2 --target x86_64 AT&T .s file, assembles it to machine 30// code via nxasm_x86_assemble, wraps it in a static ELF (entry at the 31// _start label), and writes a runnable binary. This single tool 32// replaces BOTH `gcc-as` and `ld` on the x86_64 output path -- the 33// whole-program model nxld uses (no intermediate .o). gcc/as/ld are 34// retained ONLY as one-time Wheeler bootstrap + byte-exact oracle. 35// 36// license_tier: ORIGINAL 37 38import "nxasm_x86.nx" 39// DWARF v5 .debug_line builder. Its header emitter is validated against GNU readelf byte-for-byte 40// (knowledge/dwarf_debugline_fixture_2026_08_07.md) -- NOT against our own decoder, which has no 41// header support and would only have proved two organs by one author agree (debt 1786111455). 42import "nx_dwarf_line.nx" 43// DDR-009 step 2: .debug_info / .debug_abbrev emitters. Imported EXPLICITLY -- a transitive symbol 44// is a coincidence, not a dependency. 45import "nx_dwarf_info.nx" 46import "nx_dwarf_abbrev.nx" 47import "nx_syscalls_x86_64.nx" 48// Crash diagnostics (v11 rung 1a, 2026-08-13): an assembler SEGV used to print NOTHING. 49// One call at main entry turns it into instruction+fault addresses on stderr. 50import "nx_crash.nx" 51 52// 16 MiB code + ELF buffers -- the rebuilt self-host compiler emits a 3 MB .s; 1 MiB was a 53// module-scale assumption that the compiler-scale target broke (2026-06-09). 54const NXASM_BUF: i64 = 16777216 55const NXASM_PATH_MAX: i64 = 4096 56 57// bounded strlen: byte length up to NUL, or -1 if no NUL within NXASM_PATH_MAX. 58// Caller must guarantee s is READABLE for up to NXASM_PATH_MAX bytes (never call on 59// a possibly-wild pointer the kernel has not just validated). 60func nxm_clen(s: *u8) -> i64 { 61 var n: i64 = 0 62 while n < NXASM_PATH_MAX { 63 if s[n] == (0 as u8) { return n } 64 n = n + 1 65 } 66 return 0 - 1 67} 68 69// write a NUL-terminated string to fd (bounded). Only call on a validated pointer. 70func nxm_puts(fd: i64, s: *u8) -> i64 { 71 let L: i64 = nxm_clen(s) 72 if L < 0 { return sys_write(fd, "<unterminated>" as *u8, 14) } 73 return sys_write(fd, s, L) 74} 75 76// write a signed decimal i64 to fd. 77func nxm_putn(fd: i64, v: i64) -> i64 { 78 let bb: *u8 = sys_mmap(28) 79 var m: i64 = v 80 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m } 81 let t: *u8 = sys_mmap(28) 82 var k: i64 = 0 83 if m == 0 { t[0] = 48 as u8; k = 1 } 84 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 85 var i: i64 = 0 86 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 87 return sys_write(fd, bb, k) 88} 89 90func main(argc: i64, argv: *i64) -> i64 { 91 nx_crash_guard() 92 if argc < 3 { return 2 } // usage error 93 let inpath: *u8 = argv[1] as *u8 94 let outpath: *u8 = argv[2] as *u8 95 96 let lenbox: *i64 = sys_mmap(16) as *i64 97 let src: *u8 = sys_read_file(inpath, lenbox) 98 if (src as i64) == 0 { return 3 } 99 let n: i64 = lenbox[0] 100 if n <= 0 { return 4 } 101 102 let code: *u8 = sys_mmap(NXASM_BUF) 103 let entrybox: *i64 = sys_mmap(16) as *i64 104 let code_len: i64 = nxasm_x86_assemble(src, n, code, NXASM_BUF, entrybox) 105 if code_len < 0 { return 5 } 106 107 // ---- DEBUG LINE (DDR-002) ------------------------------------------------ 108 // The .s carries .file/.loc ONLY when the compiler was run with -g, so the presence of rows 109 // IS the opt-in: no rows -> dbg_len 0 -> x86_build_elf_dbg_at returns the original bytes and 110 // this build is byte-identical to a toolchain with no DWARF at all. 111 // Addresses are made ABSOLUTE here (X86_BASE + X86_HDRLEN + text offset) because a debugger 112 // hands back a runtime PC, not a section offset -- the assembler is the first place that knows 113 // both, which is exactly why the line table has to be built here and not in the compiler. 114 var dbg_ptr: *u8 = 0 as *u8 115 var dbg_len: i64 = 0 116 if axc_n_loc > 0 { 117 if axc_n_dfile > 0 { 118 let dw: *NxDwLine = nx_dwline_new(65536) 119 nx_dwline_header(dw, src, axc_dfile_off, axc_dfile_len, axc_n_dfile) 120 nx_dwline_set_address(dw, X86_BASE + X86_HDRLEN + axc_loc_off[0]) 121 var r: i64 = 0 122 while r < axc_n_loc { 123 nx_dwline_record(dw, X86_BASE + X86_HDRLEN + axc_loc_off[r], 124 axc_loc_file[r], axc_loc_line[r]) 125 r = r + 1 126 } 127 nx_dwline_end_sequence(dw) 128 nx_dwline_finish(dw) 129 dbg_ptr = nx_dwline_bytes(dw) 130 dbg_len = nx_dwline_size(dw) 131 } 132 } 133 134 // ---- DEBUG INFO (DDR-009 step 2) -------------------------------------------------------- 135 // .debug_line answers "which source line is this address"; .debug_info answers "which FUNCTION 136 // am I in", which is what makes a backtrace name its frames. Both are needed and neither 137 // substitutes for the other. 138 // Gated on the SAME signal as the line table -- the presence of .type rows, which only appear 139 // when the compiler ran under -g -- so a non-debug build still emits nothing and stays 140 // byte-identical (proven by nx_nxasm_neutral_gate). 141 var info_ptr: *u8 = 0 as *u8 142 var info_len: i64 = 0 143 var abbr_ptr: *u8 = 0 as *u8 144 var abbr_len: i64 = 0 145 if axc_n_fn > 0 { 146 if axc_n_loc > 0 { 147 // Abbrev table: 1 = compile_unit (has children), 2 = subprogram (leaf). 148 let ab: *NxDwAbbrev = nx_dwabbrev_new(4096) 149 nx_dwabbrev_begin(ab, NX_DW_TAG_compile_unit, 1) 150 nx_dwabbrev_attr(ab, NX_DW_AT_name, NX_DW_FORM_string) 151 nx_dwabbrev_attr(ab, NX_DW_AT_low_pc, NX_DW_FORM_addr) 152 nx_dwabbrev_attr(ab, NX_DW_AT_high_pc, NX_DW_FORM_addr) 153 nx_dwabbrev_end(ab) 154 nx_dwabbrev_begin(ab, NX_DW_TAG_subprogram, 0) 155 nx_dwabbrev_attr(ab, NX_DW_AT_name, NX_DW_FORM_string) 156 nx_dwabbrev_attr(ab, NX_DW_AT_low_pc, NX_DW_FORM_addr) 157 nx_dwabbrev_attr(ab, NX_DW_AT_high_pc, NX_DW_FORM_addr) 158 nx_dwabbrev_end(ab) 159 nx_dwabbrev_finish(ab) 160 161 let df: *NxDwInfo = nx_dwinfo_new(65536) 162 nx_dwinfo_cu_begin(df, 0) 163 nx_dwinfo_die_open(df, 1) 164 let cuname: *u8 = sys_mmap(512) 165 var cn: i64 = 0 166 if axc_n_dfile > 0 { 167 while cn < axc_dfile_len[0] { cuname[cn] = src[axc_dfile_off[0] + cn]; cn = cn + 1 } 168 } 169 cuname[cn] = 0 as u8 170 nx_dwinfo_attr_string(df, cuname) 171 nx_dwinfo_attr_addr(df, X86_BASE + X86_HDRLEN) 172 nx_dwinfo_attr_addr(df, X86_BASE + X86_HDRLEN + code_len) 173 174 let fname: *u8 = sys_mmap(512) 175 var fi: i64 = 0 176 while fi < axc_n_fn { 177 var q: i64 = 0 178 var flen: i64 = axc_fn_len[fi] 179 if flen > 500 { flen = 500 } 180 while q < flen { fname[q] = src[axc_fn_off[fi] + q]; q = q + 1 } 181 fname[flen] = 0 as u8 182 // high_pc is DERIVED, never stored: the next function starts where this one ends, 183 // and the last runs to the end of .text. Storing it would be a second source of 184 // truth that can drift from the table it was derived from. 185 var hi: i64 = code_len 186 if fi + 1 < axc_n_fn { hi = axc_fn_low[fi + 1] } 187 nx_dwinfo_die_open(df, 2) 188 nx_dwinfo_attr_string(df, fname) 189 nx_dwinfo_attr_addr(df, X86_BASE + X86_HDRLEN + axc_fn_low[fi]) 190 nx_dwinfo_attr_addr(df, X86_BASE + X86_HDRLEN + hi) 191 fi = fi + 1 192 } 193 nx_dwinfo_die_null(df) 194 nx_dwinfo_cu_finalize(df) 195 196 info_ptr = nx_dwinfo_bytes(df) 197 info_len = nx_dwinfo_size(df) 198 abbr_ptr = nx_dwabbrev_bytes(ab) 199 abbr_len = nx_dwabbrev_size(ab) 200 nxm_puts(2, "nxasm_x86: debug-info subprograms=" as *u8) 201 nxm_putn(2, axc_n_fn) 202 nxm_puts(2, "\n" as *u8) 203 } 204 } 205 206 let elf: *u8 = sys_mmap(NXASM_BUF + 262144) 207 let total: i64 = x86_build_elf_dbg3_at(code, code_len, entrybox[0], elf, dbg_ptr, dbg_len, 208 info_ptr, info_len, abbr_ptr, abbr_len) 209 210 // BOUNDARY VALIDATION (rule 12): outpath is external (argv[2]). R1-T1-004 -- a 211 // parent-residue uninit-read once corrupted this slot, surfacing as a SILENT rc=6. 212 // NULL-check only here (a deref of a possibly-wild pointer would segfault; we let the 213 // kernel validate the string via openat, which faults safely with EFAULT). 214 if (outpath as i64) == 0 { 215 nxm_puts(2, "nxasm: FATAL outpath argv[2] is NULL -> rc=8\n" as *u8) 216 nxm_puts(2, " why the build stopped: the assembler was started without an output path, so there is nowhere to write the ELF.\n" as *u8) 217 nxm_puts(2, " fix: call nxasm_x86 <input.s> <output.elf>; a build lane that reached here dropped its second argument -- check the lane, not the assembler.\n" as *u8) 218 return 8 219 } 220 let fd: i64 = sys_openat_wr(outpath, 0x1ff) // 0777 221 if fd < 0 { 222 // LOUD rc=6 (was a silent generic return): name the openat errno + the pointer 223 // VALUE so a future R1-T1-004 recurrence is debuggable. Deref-print the path 224 // string ONLY when not EFAULT (-14) -- a wild residue pointer must never be 225 // dereferenced in userspace; the kernel already proved it unreadable. 226 nxm_puts(2, "nxasm: FATAL openat-WR failed openat_rc=" as *u8) 227 nxm_putn(2, fd) 228 nxm_puts(2, " outpath_ptr=" as *u8) 229 nxm_putn(2, outpath as i64) 230 if fd != (0 - 14) { 231 nxm_puts(2, " path='" as *u8) 232 nxm_puts(2, outpath) 233 nxm_puts(2, "'" as *u8) 234 } 235 nxm_puts(2, "\n" as *u8) 236 nxm_puts(2, " why the build stopped: the output file could not be opened for writing (the openat errno above says why: a missing directory, no permission, or a wild pointer for the path), so the ELF has nowhere to land.\n" as *u8) 237 nxm_puts(2, " fix: create the output directory or fix its permissions; an openat_rc of -14 (EFAULT) means the path pointer itself was invalid -- a build-lane defect, report it with this line.\n" as *u8) 238 return 6 239 } 240 let w: i64 = sys_write(fd, elf, total) 241 sys_close(fd) 242 if w != total { return 7 } 243 return 0 244}