6l: port ET_DYN dynamic linking to the ww side

Ports cmd/6l/{dyn,dynout}.c into selfhost/cmd/6l/{dyn,dynout}.ww:
ET_DYN .so loading + PT_INTERP/PT_DYNAMIC ELF emission with .rela.plt,
.gnu.version_r, BIND_NOW. lsym grows dyn fields; pass.ww promotes
undefs to dyn; out.ww dispatches; main.ww takes -L/-l. The ww driver
forwards -L/-l to 6l_ww so 'ww_ww build snake.ww -L /usr/lib -l ncurses
-l c' runs without cc.

Test 996 pins byte-identical output to C-6l on snake.

'make bootstrap' gains a fourth stage with cmp ww3 == ww4, proving
ww3 is byte-stable when used as a compiler — not just a coincidental
two-stage equilibrium.

Four wwstage 6c cgen quirks surfaced and are documented in dynout.ww's
header (two-level field-write through a pointer field, (scalar, str)
tuple returns, def : str, ≤6 arg calling convention).
This commit is contained in:
2026-05-11 12:47:36 +09:00
parent edfc4273f6
commit e217cd32d1
13 changed files with 3361 additions and 111 deletions

View File

@@ -150,7 +150,8 @@ $(BIN)/6a_ww: selfhost/cmd/6a/main.ww selfhost/cmd/6a/types.ww \
# Built like 6a_ww. Needs -I selfhost/cmd/6l for the local sym/obj/pass/
# out modules and -I selfhost/cmd/wwc to find `mem`.
$(BIN)/6l_ww: selfhost/cmd/6l/main.ww selfhost/cmd/6l/sym.ww \
selfhost/cmd/6l/obj.ww selfhost/cmd/6l/pass.ww \
selfhost/cmd/6l/obj.ww selfhost/cmd/6l/dyn.ww \
selfhost/cmd/6l/pass.ww selfhost/cmd/6l/dynout.ww \
selfhost/cmd/6l/out.ww selfhost/cmd/wwc/mem.ww \
$(BIN)/ww $(BIN)/6c $(BIN)/6a $(BIN)/6l \
$(LIB)/libwwrt.a | $(BIN)
@@ -188,7 +189,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_6c $(BIN)/test_6a $(BIN)/test_6l $(BIN)/test_arch \
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
$(BIN)/test_selfhost $(BIN)/test_6a_ww $(BIN)/test_6l_ww \
$(BIN)/test_6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild
$(BIN)/test_6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
$(BIN)/test_dyn_ww
$(BIN)/test_smoke: test/wwc/000_smoke.c $(LIB)/libwwc.a | $(BIN)
$(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwwc
@@ -252,6 +254,10 @@ $(BIN)/test_self_rebuild: test/wwc/995_self_rebuild.c $(BIN)/ww_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_dyn_ww: test/wwc/996_dyn_ww.c $(BIN)/ww $(BIN)/6l $(BIN)/6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
test: all $(TESTS)
@WW=$(BIN)/ww BIN=$(BIN) sh test/run
@@ -264,17 +270,23 @@ clean:
rm -rf $(OUT)
# ---- bootstrap (phase 10) ----------------------------------------------
# Three-stage self-host: Cstage (these C tools) → ww1 → ww2 → ww3 with
# cmp ww2 == ww3. Stage 1 is wwdump_ww (the ww-built frontend); stages
# 2 and 3 assemble with 6a_ww and link with 6l_ww — every tool below
# the driver is now in ww. The C `ww` driver still orchestrates the
# pipeline (porting it is the remaining phase-10 work).
# Four-stage self-host: Cstage (these C tools) → ww1 → ww2 → ww3 → ww4
# with cmp ww2 == ww3 and cmp ww3 == ww4. Stage 1 is wwdump_ww (the
# ww-built frontend); stages 2/3/4 assemble with 6a_ww and link with
# 6l_ww — every tool below the driver is now in ww. The C `ww` driver
# still orchestrates the pipeline (porting it is the remaining phase-10
# work).
#
# Why a fourth stage? Stage 3 proves ww1 and ww2 (which differ in how
# they were built) emit the same machine code. Stage 4 proves ww3 is
# byte-stable when used as a compiler — i.e. the fixed point is truly
# fixed, not merely a coincidental two-stage equilibrium.
BS = $(OUT)/bootstrap
bootstrap: all
@echo "=== stage 0 (Cstage): C-built tools ==="
@echo " $(BIN)/ww $(BIN)/6c $(BIN)/6a $(BIN)/6l"
@echo
@echo "=== stage 1: ww-built tools, used to drive stages 2 and 3 ==="
@echo "=== stage 1: ww-built tools, used to drive stages 2/3/4 ==="
@ls -l $(BIN)/wwdump_ww $(BIN)/6a_ww $(BIN)/6l_ww $(BIN)/ww_ww
@rm -rf $(BS) && mkdir -p $(BS)
@echo
@@ -290,12 +302,21 @@ bootstrap: all
@$(BIN)/6l_ww -o $(BS)/ww3 $(BS)/ww3.o $(LIB)/libwwrt.a
@ls -l $(BS)/ww3
@echo
@echo "=== stage 4: ww4 = ww3 self-compiles main.combined.ww ==="
@$(BS)/ww3 -c selfhost/cmd/wwdump/main.combined.ww > $(BS)/ww4.s
@$(BIN)/6a_ww -o $(BS)/ww4.o $(BS)/ww4.s
@$(BIN)/6l_ww -o $(BS)/ww4 $(BS)/ww4.o $(LIB)/libwwrt.a
@ls -l $(BS)/ww4
@echo
@echo "=== fixed-point gates ==="
@cmp $(BS)/ww2.s $(BS)/ww3.s && echo " ww2.s == ww3.s ✓"
@cmp $(BS)/ww2.o $(BS)/ww3.o && echo " ww2.o == ww3.o ✓ (byte-identical .o)"
@cmp $(BS)/ww2 $(BS)/ww3 && echo " ww2 == ww3 ✓ (byte-identical exe)"
@cmp $(BS)/ww3.s $(BS)/ww4.s && echo " ww3.s == ww4.s ✓"
@cmp $(BS)/ww3.o $(BS)/ww4.o && echo " ww3.o == ww4.o ✓ (byte-identical .o)"
@cmp $(BS)/ww3 $(BS)/ww4 && echo " ww3 == ww4 ✓ (byte-identical exe, ww3 is byte-stable as a compiler)"
@echo
@echo "BOOTSTRAP OK: ww-cgen + 6a_ww + 6l_ww reach a fixed point."
@echo "BOOTSTRAP OK: ww-cgen + 6a_ww + 6l_ww reach a fixed point that holds across two iterations."
@echo
@echo "Phase 10 remaining work:"
@echo " - delete cmd/wwc/ cmd/6c/ cmd/6a/ cmd/6l/ cmd/ww/ in the final commit"