diff --git a/cmd/w6l/out.c b/cmd/w6l/out.c index 848001f8..6d35817a 100644 --- a/cmd/w6l/out.c +++ b/cmd/w6l/out.c @@ -63,11 +63,24 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry) const u64 rx_end = text_off + l->textlen; const int has_data = (l->datalen > 0); + /* BSS optimisation: trailing zero bytes in .data can be left out + * of the file. The loader zero-fills the gap between p_filesz and + * p_memsz, so this shrinks the binary without changing semantics. + * Scan after l_relocate has applied DATAR patches — anything still + * zero at the tail genuinely is zero-init. */ + u64 bsslen = 0; + if (has_data) { + while (bsslen < l->datalen + && l->data[l->datalen - 1 - bsslen] == 0) + bsslen++; + } + const u64 data_file_len = l->datalen - bsslen; + /* data goes at the next page boundary so the loader can grant a * fresh page of R+W permissions without overlapping the R+X mapping. */ const u64 data_off = has_data ? ((rx_end + page - 1) & ~(page - 1)) : 0; const u64 data_va = has_data ? (base + data_off) : 0; - const u64 file_end = has_data ? (data_off + l->datalen) : rx_end; + const u64 file_end = has_data ? (data_off + data_file_len) : rx_end; (void)data_va; Ehdr eh = {0}; @@ -104,7 +117,7 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry) phw.p_offset = data_off; phw.p_vaddr = base + data_off; phw.p_paddr = base + data_off; - phw.p_filesz = l->datalen; + phw.p_filesz = data_file_len; phw.p_memsz = l->datalen; phw.p_align = page; } @@ -119,11 +132,11 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry) if (l->textlen) fwrite(l->text, 1, l->textlen, f); - if (has_data) { + if (has_data && data_file_len > 0) { /* pad to data_off */ here = ftell(f); for (long i = here; i < (long)data_off; i++) fputc(0, f); - fwrite(l->data, 1, l->datalen, f); + fwrite(l->data, 1, data_file_len, f); } (void)file_end; diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 4f6a9d68..b1b3a235 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -2398,6 +2398,21 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64); }; + // BSS optimisation: trailing zero bytes in .data can be left + // out of the file. The loader zero-fills the gap between + // p_filesz and p_memsz. Scan after l_relocate has applied any + // DATAR patches — anything still zero at the tail genuinely is + // zero-init. Matches cmd/w6l/out.c byte-for-byte. + let bsslen: u64 = 0u64; + if (hasdata) { + for (bsslen < l.datalen) { + let b: u8 = l.data[l.datalen - 1u64 - bsslen]; + if (b != 0u8) { break; }; + bsslen += 1u64; + }; + }; + let datafilelen: u64 = l.datalen - bsslen; + // One contiguous header buffer covering [0..0x1000), then .text. let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap @@ -2441,7 +2456,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru64(hdr, 128u64, dataoff); // p_offset wru64(hdr, 136u64, base + dataoff); // p_vaddr wru64(hdr, 144u64, base + dataoff); // p_paddr - wru64(hdr, 152u64, l.datalen); // p_filesz + wru64(hdr, 152u64, datafilelen); // p_filesz wru64(hdr, 160u64, l.datalen); // p_memsz wru64(hdr, 168u64, PAGE_SZ); // p_align }; @@ -2463,8 +2478,10 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; if (n2 != l.textlen: i64) { return -1; }; }; - if (hasdata) { - // Pad to the page-aligned data offset, then write .data. + if (hasdata && datafilelen > 0u64) { + // Pad to the page-aligned data offset, then write only + // the non-zero prefix of .data. The rest is BSS — the + // loader zero-fills from p_filesz to p_memsz. let here: u64 = TEXT_OFF + l.textlen; let zero: u8 = 0u8; for (here < dataoff) { @@ -2475,13 +2492,13 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; here += 1u64; }; - let r4: (i64 | os.oserror) = os.writeall(fd, l.data, l.datalen); + let r4: (i64 | os.oserror) = os.writeall(fd, l.data, datafilelen); let n4: i64 = 0i64; match (r4) { case let v: i64 => n4 = v; case let e: os.oserror => return -1; }; - if (n4 != l.datalen: i64) { return -1; }; + if (n4 != datafilelen: i64) { return -1; }; }; return 0; }; diff --git a/selfhost/cmd/w6l/out.ww b/selfhost/cmd/w6l/out.ww index 88e3d55c..df031906 100644 --- a/selfhost/cmd/w6l/out.ww +++ b/selfhost/cmd/w6l/out.ww @@ -63,6 +63,21 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64); }; + // BSS optimisation: trailing zero bytes in .data can be left + // out of the file. The loader zero-fills the gap between + // p_filesz and p_memsz. Scan after l_relocate has applied any + // DATAR patches — anything still zero at the tail genuinely is + // zero-init. Matches cmd/w6l/out.c byte-for-byte. + let bsslen: u64 = 0u64; + if (hasdata) { + for (bsslen < l.datalen) { + let b: u8 = l.data[l.datalen - 1u64 - bsslen]; + if (b != 0u8) { break; }; + bsslen += 1u64; + }; + }; + let datafilelen: u64 = l.datalen - bsslen; + // One contiguous header buffer covering [0..0x1000), then .text. let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap @@ -106,7 +121,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru64(hdr, 128u64, dataoff); // p_offset wru64(hdr, 136u64, base + dataoff); // p_vaddr wru64(hdr, 144u64, base + dataoff); // p_paddr - wru64(hdr, 152u64, l.datalen); // p_filesz + wru64(hdr, 152u64, datafilelen); // p_filesz wru64(hdr, 160u64, l.datalen); // p_memsz wru64(hdr, 168u64, PAGE_SZ); // p_align }; @@ -128,8 +143,10 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; if (n2 != l.textlen: i64) { return -1; }; }; - if (hasdata) { - // Pad to the page-aligned data offset, then write .data. + if (hasdata && datafilelen > 0u64) { + // Pad to the page-aligned data offset, then write only + // the non-zero prefix of .data. The rest is BSS — the + // loader zero-fills from p_filesz to p_memsz. let here: u64 = TEXT_OFF + l.textlen; let zero: u8 = 0u8; for (here < dataoff) { @@ -140,13 +157,13 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; here += 1u64; }; - let r4: (i64 | os.oserror) = os.writeall(fd, l.data, l.datalen); + let r4: (i64 | os.oserror) = os.writeall(fd, l.data, datafilelen); let n4: i64 = 0i64; match (r4) { case let v: i64 => n4 = v; case let e: os.oserror => return -1; }; - if (n4 != l.datalen: i64) { return -1; }; + if (n4 != datafilelen: i64) { return -1; }; }; return 0; }; diff --git a/test/wcc/620_data_link.c b/test/wcc/620_data_link.c index 0a8d1a87..b23e4290 100644 --- a/test/wcc/620_data_link.c +++ b/test/wcc/620_data_link.c @@ -239,6 +239,84 @@ test_no_data_single_load(const char *bin) return rc; } +/* Test 5: BSS optimisation — trailing zero bytes in .data are dropped + * from the file (p_filesz < p_memsz) and the loader zero-fills the gap. + * Asserts that a trailing zero-init slot doesn't bloat the binary. */ +static int +test_bss_filesz(const char *bin) +{ + char exe[64]; + snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x5", getpid()); + /* Two DATAW slots: a 1-byte non-zero followed by a 31-byte zero + * run. Memsz must cover all 32B; filesz should stop after the 1B + * non-zero (so file_data == 1, memsz_data == 32). */ + const char *body = + "TEXT _start,$0\n" + "\tMOVQ\tnz(SB), DI\n" + "\tMOVQ\t$60, AX\n" + "\tSYSCALL\n" + "DATAW nz(SB),\"\\x07\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n" + "DATAW zlong(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" + "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" + "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"; + if (build(bin, body, exe) != 0) { + fprintf(stderr, "test_bss_filesz: build failed\n"); + return -1; + } + uint8_t *buf = NULL; + size_t n = 0; + if (slurp(exe, &buf, &n) < 0) { + fprintf(stderr, "test_bss_filesz: cannot read exe\n"); + unlink(exe); + return -1; + } + int rc = 0; + const Ehdr *eh = (const Ehdr *)buf; + const Phdr *ph = (const Phdr *)(buf + eh->e_phoff); + int saw_rw = 0; + for (int i = 0; i < eh->e_phnum; i++) { + if (ph[i].p_type != PT_LOAD) continue; + if (ph[i].p_flags != (PF_R | PF_W)) continue; + saw_rw = 1; + if (ph[i].p_filesz >= ph[i].p_memsz) { + fprintf(stderr, "test_bss_filesz: filesz=%lu, " + "memsz=%lu (want filesz