commit 67b8fdb86aedb707c973b5f8849f2bf2304a1e0a
parent 600fbdc515633243f8e9ef9b04f9c84ff07af210
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 3 May 2026 18:24:24 -0700
cc: update to m1pp+hex2pp
Diffstat:
4 files changed, 120 insertions(+), 111 deletions(-)
diff --git a/Makefile b/Makefile
@@ -142,11 +142,17 @@ $(IMAGE_STAMPS): build/%/.image: scripts/Containerfile.busybox
#
# Stage 1: vendored hex0-seed -> M0/hex2-0/catm in the container.
-TOOLS_M0 := $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0)
+TOOLS_M0 := $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0)
+TOOLS_CATM := $(foreach a,$(ALL_ARCHES),build/$(a)/tools/catm)
tools: $(TOOLS_DIR)/M0
-$(TOOLS_M0): build/%/tools/M0: scripts/boot1.sh build/%/.image \
+# boot1.sh produces M0, hex2-0, and catm in one shot. Grouped targets
+# (`&:`) tell make they're all outputs of a single recipe execution, so
+# downstream rules can depend on whichever tool they actually invoke
+# (e.g. cc/scheme1 tests need only catm, not M0/hex2-0).
+build/%/tools/M0 build/%/tools/catm build/%/tools/hex2-0 &: \
+ scripts/boot1.sh build/%/.image \
vendor/seed/%/hex0-seed vendor/seed/%/hex0.hex0 \
vendor/seed/%/hex1.hex0 vendor/seed/%/hex2.hex1 \
vendor/seed/%/catm.hex2 vendor/seed/%/M0.hex2 \
@@ -236,8 +242,9 @@ $(SCHEME1_BINS): build/%/scheme1/scheme1: $(SCHEME1_SRC) $(P1PP_BUILD_DEPS)
$(call PODMAN,$*) sh scripts/boot-build-p1pp.sh $@ $(SCHEME1_SRC)
# cc.scm: catm prelude + cc.scm + main.scm entry into one source the
-# scheme1 interpreter can run. Catm runs inside the per-arch container.
-$(CC_BINS): build/%/cc/cc.scm: $(CC_SRCS) build/%/.image build/%/tools/M0
+# scheme1 interpreter can run. Catm runs inside the per-arch container;
+# only catm (not M0/hex2-0) is needed.
+$(CC_BINS): build/%/cc/cc.scm: $(CC_SRCS) build/%/.image build/%/tools/catm
mkdir -p $(@D)
$(call PODMAN,$*) build/$*/tools/catm $@ $(CC_SRCS)
@@ -434,23 +441,25 @@ TEST_P1_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/tools/M0 P1/P1-$(a).M1 \
build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp vendor/seed/$(a)/ELF.hex2)
-# scheme1 suite per-arch deps: image, expander, hex2pp, scheme1 binary.
+# scheme1 suite per-arch deps: image, expander, hex2pp, scheme1 binary,
+# and catm (boot-run-scheme1.sh prepends prelude.scm with catm).
# (run-tests.sh runs the pre-built binary against each .scm fixture; it
-# does not rebuild the interpreter per fixture.)
+# does not rebuild the interpreter per fixture.) M0/hex2-0 are not in
+# the runtime test path — they're pulled in transitively only as build
+# inputs to M1pp/hex2pp.
TEST_SCHEME1_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \
- build/$(a)/scheme1/scheme1)
+ build/$(a)/scheme1/scheme1 build/$(a)/tools/catm)
# cc-* suites: scheme1 + M1pp + hex2pp cover everything. cc-util /
# cc-lex / cc-pp byte-diff their pure transformations; cc-cg / cc
# compile the emitted P1pp through the P1pp toolchain (M1pp + hex2pp)
# and run the resulting ELF. cc.scm is only needed by the cc suite
# (it invokes the catm'd compiler against a .c fixture); the rest
-# catm their own per-suite layer list. catm comes from build/$(a)/tools/
-# (built once during the seed bootstrap; only the cc-unit catm chain
-# uses it now — the P1pp pipeline no longer touches it).
+# catm their own per-suite layer list. The runtime path uses only catm
+# from build/$(a)/tools/ — M0/hex2-0 are not invoked.
TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \
- build/$(a)/.image build/$(a)/tools/M0 \
+ build/$(a)/.image build/$(a)/tools/catm \
build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \
build/$(a)/scheme1/scheme1)
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -2604,7 +2604,8 @@
;; signed/unsigned dispatch, pointer scaling.
;;
;; Output uses libp1pp's structured macros (%fn, %ifelse_nez,
-;; %loop_tag, %break, %continue) per docs/LIBP1PP.md.
+;; %break, %continue) per docs/LIBP1PP.md. Function-local control-flow
+;; labels are hex2++ dotted labels inside %fn's .scope.
;;
;; Frame layout:
;; [sp + 0 .. staging*8) outgoing-arg staging
@@ -2931,14 +2932,12 @@
(cg-in-fn?-set! cg #t)
(cg-vstack-set! cg '())
(cg-frame-hi-set! cg 0)
- ;; cg-label-ctr is NOT reset per-fn. Loop tags reach libp1pp's
- ;; %loop_tag / %while_tag_* / %break / %continue macros, which emit
- ;; `::tag_top` / `::tag_end` — scope-local to the enclosing %fn —
- ;; so within-TU collisions are already prevented by M1pp scoping.
- ;; Switch dispatch labels (`sw_disp_L<N>`) likewise emit through
- ;; `::` and inherit %fn's scope. Keeping the counter monotonic
- ;; across functions is no longer required for correctness, just
- ;; for stable, readable label names in expanded.M1 traces.
+ ;; cg-label-ctr is NOT reset per-fn. Compiler-internal labels are
+ ;; emitted as dotted hex2++ locals inside %fn's .scope (and sometimes
+ ;; nested .scope blocks), so within-TU collisions are already prevented
+ ;; by local lookup. Keeping the counter monotonic across functions is
+ ;; no longer required for correctness, just for stable, readable label
+ ;; names in expanded.M1 traces.
(cg-max-outgoing-set! cg 0)
(cg-fn-meta-set! cg '())
(%cg-fn-set! cg '%fn-name name)
@@ -3080,7 +3079,7 @@
(buf-push! tb ")\n"))))
(buf-drain! tb (cg-fn-buf cg))
;; ret block: ≤8B → a0; 9–16B → a0+a1; >16B sret → a0 = saved sret ptr.
- (buf-push! tb "::ret\n")
+ (buf-push! tb ":.ret\n")
(let ((rk (ctype-kind ret-type))
(sret? (%cg-fn-get cg '%fn-sret?)))
(cond
@@ -3850,7 +3849,7 @@
(sret? (%cg-fn-get cg '%fn-sret?)))
(cond
((eq? rk 'void)
- (%cg-emit-many cg (list "%b(&::ret)\n")))
+ (%cg-emit-many cg (list "%b(&.ret)\n")))
((or (eq? rk 'struct) (eq? rk 'union))
;; struct-by-value: ≤16B (A1) → ret-slot; >16B (A2 sret) → *sret-slot.
(let* ((p (cg-pop cg)) (sz (ctype-size ret-type)))
@@ -3860,15 +3859,15 @@
(cond
(sret?
(%cg-emit-ld-slot cg 't2 (%cg-fn-get cg '%fn-sret-slot)))
- (else
+ (else
(%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg ret-slot))))
(%cg-emit-byte-copy cg 't2 't0 't1 sz)
- (%cg-emit-many cg (list "%b(&::ret)\n"))))
+ (%cg-emit-many cg (list "%b(&.ret)\n"))))
(else
(let ((p (cg-pop cg)))
(%cg-load-opnd-into cg p 'a0)
(%cg-emit-st-slot cg 'a0 ret-slot)
- (%cg-emit-many cg (list "%b(&::ret)\n")))))))
+ (%cg-emit-many cg (list "%b(&.ret)\n")))))))
;; --------------------------------------------------------------------
;; Structured control flow
@@ -3922,23 +3921,26 @@
;; body-thunk receives the loop tag as its argument; parser uses
;; that tag for cg-break / cg-continue inside the body.
(let ((tag (%cg-fresh-loop-tag cg)))
- (%cg-emit-many cg (list "%loop_tag(" tag ", {\n"))
+ (%cg-emit-many cg (list ".scope\n"
+ ":.top\n"))
(head-thunk)
(cond
((zero? (cg-depth cg)) 0)
(else
(let ((c (cg-pop cg)))
(%cg-load-opnd-into cg c 't0)
- (%cg-emit-many cg (list "%if_eqz(t0, { %break(" tag ") })\n")))))
+ (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))))
(body-thunk tag)
- (%cg-emit-many cg (list "})\n"))
+ (%cg-emit-many cg (list "%b(&.top)\n"
+ ":.end\n"
+ ".endscope\n"))
tag))
(define (cg-break cg tag)
- (%cg-emit-many cg (list "%break(" tag ")\n")))
+ (%cg-emit-many cg (list "%break\n")))
(define (cg-continue cg tag)
- (%cg-emit-many cg (list "%continue(" tag ")\n")))
+ (%cg-emit-many cg (list "%continue\n")))
;; --------------------------------------------------------------------
;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a 16-slot
@@ -4031,16 +4033,20 @@
;; --------------------------------------------------------------------
;; Labels and unconditional goto.
-;; user_<name> namespace keeps the user's label space disjoint from
-;; the compiler-internal ::ret and ::lbl_<n>. Labels resolve through
-;; libp1pp's %scope mechanism, so forward references inside the same
-;; %fn block work without explicit forward declaration.
+;; C labels have function scope, even when the labelled statement appears
+;; inside a nested block/loop. Emit them as function-qualified global
+;; labels rather than dotted hex2++ locals, because dotted definitions
+;; inside a nested `.scope` would be invisible to gotos outside it.
;; --------------------------------------------------------------------
+(define (%cg-user-label cg name-bv)
+ (let ((fn (%cg-fn-get cg '%fn-name)))
+ (bv-cat (list "cc__" fn "__user_" name-bv))))
+
(define (cg-emit-label cg name-bv)
- (%cg-emit-many cg (list "::user_" name-bv "\n")))
+ (%cg-emit-many cg (list ":" (%cg-user-label cg name-bv) "\n")))
(define (cg-goto cg name-bv)
- (%cg-emit-many cg (list "%b(&::user_" name-bv ")\n")))
+ (%cg-emit-many cg (list "%b(&" (%cg-user-label cg name-bv) ")\n")))
;; --------------------------------------------------------------------
;; switch
@@ -4059,8 +4065,8 @@
(disp-lbl (bytevector-append "sw_disp_" tag)))
(%cg-load-opnd-into cg p 't0)
(%cg-emit-st-slot cg 't0 off)
- (%cg-emit-many cg (list "%loop_tag(" tag ", {\n"
- "%b(&::" disp-lbl ")\n"))
+ (%cg-emit-many cg (list ".scope\n"
+ "%b(&." disp-lbl ")\n"))
(%swctx off tag #f)))
(define (cg-switch-case cg sw const-int)
@@ -4070,12 +4076,12 @@
(cur (or (%cg-fn-get cg key) '()))
(entry (cons const-int lbl)))
(%cg-fn-set! cg key (cons entry cur))
- (%cg-emit-many cg (list "::" lbl "\n"))))
+ (%cg-emit-many cg (list ":." lbl "\n"))))
(define (cg-switch-default cg sw)
(let ((lbl (%cg-fresh-lbl cg)))
(swctx-default-lbl-set! sw lbl)
- (%cg-emit-many cg (list "::" lbl "\n"))))
+ (%cg-emit-many cg (list ":." lbl "\n"))))
(define (cg-switch-end cg sw)
(let* ((tag (swctx-end-tag sw))
@@ -4083,20 +4089,21 @@
(cases (reverse (or (%cg-fn-get cg key) '())))
(default-lbl (swctx-default-lbl sw))
(disp-lbl (bytevector-append "sw_disp_" tag)))
- (%cg-emit-many cg (list "%break(" tag ")\n"
- "::" disp-lbl "\n"))
+ (%cg-emit-many cg (list "%break\n"
+ ":." disp-lbl "\n"))
(%cg-emit-many cg (list "%ld(t0, sp, "
(%cg-slot-expr cg (swctx-ctrl-slot sw)) ")\n"))
(for-each
(lambda (c)
(%cg-emit-many cg (list "%switch_case(t0, t1, "
- (%n (car c)) ", &::" (cdr c) ")\n")))
+ (%n (car c)) ", &." (cdr c) ")\n")))
cases)
(cond
- (default-lbl (%cg-emit-many cg (list "%b(&::" default-lbl ")\n")))
+ (default-lbl (%cg-emit-many cg (list "%b(&." default-lbl ")\n")))
(else 0))
- (%cg-emit-many cg (list "%break(" tag ")\n"
- "})\n"))))
+ (%cg-emit-many cg (list "%break\n"
+ ":.end\n"
+ ".endscope\n"))))
;; --------------------------------------------------------------------
;; Globals and data
@@ -4109,9 +4116,8 @@
;; (piece ...) — initialized in .data; pieces concatenated.
;;
;; Each piece is either:
-;; <bytevector> — raw bytes; emitted as `'XXXX...'` M0
-;; quoted-hex chunks (64 bytes / 128 hex
-;; chars per line).
+;; <bytevector> — raw bytes; emitted as bare hex chunks
+;; (64 bytes / 128 hex chars per line).
;; (label-ref . <label-bv>) — 8-byte pointer slot containing &label;
;; emitted as `&<label> %(0)` (4B label ref +
;; 4B zero pad).
@@ -4126,10 +4132,12 @@
(define (cg-emit-global cg sym init)
(let* ((lbl (%cg-sym-label sym))
(sz (ctype-size (sym-type sym)))
- (size (if (< sz 0) 8 sz)))
+ (size (if (< sz 0) 8 sz))
+ (al (max 1 (ctype-align (sym-type sym)))))
(cond
(init
- (buf-push! (cg-data cg) (bv-cat (list "\n:" lbl "\n")))
+ (buf-push! (cg-data cg) (bv-cat (list "\n.align " (%n al) "\n:"
+ lbl "\n")))
(let walk ((ps init))
(cond
((null? ps) 0)
@@ -4138,7 +4146,7 @@
(walk (cdr ps))))))
(else
(buf-push! (cg-bss cg)
- (bv-cat (list "\n:" lbl "\n"
+ (bv-cat (list "\n.align " (%n al) "\n:" lbl "\n"
(let zero-loop ((rem size) (acc '()))
(cond
((<= rem 0) (bv-cat (reverse acc)))
@@ -4189,8 +4197,10 @@
(cg-str-pool-set! cg
(alist-set bv-content lbl (cg-str-pool cg)))
(buf-push! (cg-data cg)
- (bv-cat (append (list "\n:" lbl "\n")
- (%cg-bv->hex-lines bv-content #t))))
+ (bv-cat (append (list "\n.align " (%n %CG-STR-ALIGN)
+ "\n:" lbl "\n")
+ (%cg-bv->hex-lines bv-content #t)
+ (list ".align " (%n %CG-STR-ALIGN) "\n"))))
lbl)))))
;; Mint a fresh, never-recurring label for an unnamed file-scope
@@ -4204,34 +4214,23 @@
(cg-label-ctr-set! cg (+ n 1))
lbl))
-;; Render BV's bytes as `'XXXXXX'` quoted-hex M0 literals — uniform
-;; format for every byte, regardless of whether it would otherwise be
-;; printable. Avoids the `"..."` lex path entirely (m1pp's quoted-text
-;; lex has no escape mechanism, so embedded `"`, `\`, control chars,
-;; and high-bit bytes can't ride raw between the quotes), and avoids
-;; per-byte `!(N)` lines (5+× larger output). Lines are chunked to
-;; ≤128 hex chars (= 64 bytes) — M0's per-line quoted-literal buffer
-;; is 256 bytes on amd64 and overflows otherwise.
+;; Render BV's bytes as bare hex accepted directly by hex2++. Lines are
+;; chunked to ≤128 hex chars (= 64 bytes) to keep generated P1pp readable.
;;
;; If TRAILING-NUL? is #t, an extra 0x00 byte is appended to terminate
-;; a C string AND total emitted bytes are rounded up to %CG-STR-ALIGN
-;; (8). The padding makes the next label after a string land at an
-;; 8-aligned address — aarch64 BLR / 4-byte LDR fault otherwise.
-;; Padding is gated on TRAILING-NUL? because the only other caller
-;; (%cg-init-piece->bv) emits arbitrary initializer bytes whose
-;; length is sized exactly to the C-visible field; padding a 4-byte
-;; int slot to 8 would shift every following struct field. The
-;; %cg-hex-line tail already emits 0x00 for indices past LEN, so
-;; padding costs only output bytes. Returns a list of bytevectors
-;; ready for bv-cat.
+;; a C string. Alignment is emitted explicitly by callers with .align
+;; so hex2++ owns padding instead of cc.scm manufacturing zero bytes.
+;; The other caller (%cg-init-piece->bv) emits arbitrary initializer
+;; bytes whose length is sized exactly to the C-visible field; padding a
+;; 4-byte int slot to 8 would shift every following struct field.
+;; Returns a list of bytevectors ready for bv-cat.
(define %CG-HEX-CHUNK-BYTES 64)
(define %CG-STR-ALIGN 8)
(define (%cg-bv->hex-lines bv trailing-nul?)
(let* ((len (bytevector-length bv))
(logical (cond (trailing-nul? (+ len 1)) (else len)))
- (total (cond (trailing-nul? (align-up logical %CG-STR-ALIGN))
- (else logical))))
+ (total logical))
(cond
((= total 0) '())
(else
@@ -4244,17 +4243,15 @@
(else total))))
(loop end (cons (%cg-hex-line bv i end len) acc))))))))))
-;; One `'XXXX...XX'\n` line covering BV bytes [START, END). Indices
+;; One `XXXX...XX\n` line covering BV bytes [START, END). Indices
;; >= LEN render as 0x00 (used for the trailing NUL terminator).
(define (%cg-hex-line bv start end len)
(let* ((nbytes (- end start))
- (out (make-bytevector (+ 1 (* 2 nbytes) 1 1))))
- (bytevector-u8-set! out 0 (char->integer #\'))
- (let loop ((j start) (k 1))
+ (out (make-bytevector (+ (* 2 nbytes) 1))))
+ (let loop ((j start) (k 0))
(cond
((= j end)
- (bytevector-u8-set! out k (char->integer #\'))
- (bytevector-u8-set! out (+ k 1) (char->integer #\newline))
+ (bytevector-u8-set! out k (char->integer #\newline))
out)
(else
(let ((b (cond ((< j len) (bytevector-u8-ref bv j))
@@ -6531,35 +6528,37 @@
(define (parse-do-stmt ps)
(expect-kw ps 'do)
;; `continue` in a do-while must jump to the *cond test* (C11
- ;; §6.8.6.2 ¶2), not to the top of the body. cg-continue jumps to
- ;; ::tag_top, so we lay the loop out so that ::tag_top labels the
- ;; cond test and the body lives between an entry-skip and a back
- ;; edge. The macro %loop_tag isn't shaped right for this — emit
- ;; raw P1pp here, mirroring parse-for-stmt's hand-rolled layout.
+ ;; §6.8.6.2 ¶2), not to the top of the body. The scoped loop labels
+ ;; `.top` at the condition test and `.end` after the loop, so bare
+ ;; %continue / %break bind through hex2++ local lookup.
;;
;; Layout:
- ;; ::tag_body
+ ;; .scope
+ ;; :.body
;; <body>
- ;; ::tag_top ; %continue(tag) jumps here
+ ;; :.top ; %continue jumps here
;; <cond>
- ;; %if_eqz(c, %break(tag))
- ;; %b(&::tag_body)
- ;; ::tag_end
+ ;; %if_eqz(c, %break)
+ ;; %b(&.body)
+ ;; :.end
+ ;; .endscope
(let* ((cg (ps-cg ps))
(tag (%cg-fresh-loop-tag cg)))
- (%cg-emit-many cg (list "::" tag "_body\n"))
+ (%cg-emit-many cg (list ".scope\n"
+ ":.body\n"))
(push-loop-ctx! ps 'do tag #t)
(parse-stmt ps)
(pop-loop-ctx! ps)
(expect-kw ps 'while) (expect-punct ps 'lparen)
- (%cg-emit-many cg (list "::" tag "_top\n"))
+ (%cg-emit-many cg (list ":.top\n"))
(parse-expr ps) (rval! ps)
(expect-punct ps 'rparen) (expect-punct ps 'semi)
(let ((c (cg-pop cg)))
(%cg-load-opnd-into cg c 't0)
- (%cg-emit-many cg (list "%if_eqz(t0, { %break(" tag ") })\n")))
- (%cg-emit-many cg (list "%b(&::" tag "_body)\n"
- "::" tag "_end\n")))
+ (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
+ (%cg-emit-many cg (list "%b(&.body)\n"
+ ":.end\n"
+ ".endscope\n")))
#t)
(define (parse-for-stmt ps)
@@ -6581,21 +6580,23 @@
;; A C `continue` in a for-loop must run the step expression before
;; retesting the condition. Arrange the loop as:
;; jump test; top: step; test: condition; body; jump top
- (%cg-emit-many cg (list "%b(&::" tag "_test)\n"
- "::" tag "_top\n"))
+ (%cg-emit-many cg (list ".scope\n"
+ "%b(&.test)\n"
+ ":.top\n"))
(parse-saved-expr-stmt ps step-toks)
- (%cg-emit-many cg (list "::" tag "_test\n"))
+ (%cg-emit-many cg (list ":.test\n"))
(cond
((null? cond-toks) (cg-push-imm cg %t-i32 1))
(else (parse-saved-expr ps cond-toks) (rval! ps)))
(let ((c (cg-pop cg)))
(%cg-load-opnd-into cg c 't0)
- (%cg-emit-many cg (list "%if_eqz(t0, { %break(" tag ") })\n")))
+ (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
(push-loop-ctx! ps 'for tag #t)
(parse-stmt ps)
(pop-loop-ctx! ps)
- (%cg-emit-many cg (list "%b(&::" tag "_top)\n"
- "::" tag "_end\n")))
+ (%cg-emit-many cg (list "%b(&.top)\n"
+ ":.end\n"
+ ".endscope\n")))
(scope-leave! ps) #t)
(define (parse-saved-expr ps toks)
diff --git a/docs/LIBC.md b/docs/LIBC.md
@@ -205,10 +205,10 @@ library: it suppresses the auto-emitted entry stub
(`%fn(p1_main, 16, { %call(&main) })`) and the trailing `:ELF_end`,
and namespaces anonymous string labels as `PFX+"cc__str_N"` so two
cc.scm outputs in the same link don't collide on `cc__str_0..N`.
-String literals are 8-byte padded unconditionally (any TU, lib or
-exec) so labels following a string land at an aligned address —
-without it, aarch64 BLR / 4-byte LDR SIGBUS once a non-multiple-of-4
-string shows up in `.data`.
+String literals emit their bytes plus a NUL terminator, then an
+explicit `.align 8` (any TU, lib or exec) so labels following a string
+land at an aligned address — without it, aarch64 BLR / 4-byte LDR
+SIGBUS once a non-multiple-of-4 string shows up in `.data`.
Wired together, the link is just `catm`:
@@ -432,10 +432,10 @@ That's tracked in [TCC.md](TCC.md), not here.
### cc.scm bugs surfaced by Phase A
-The four link-composition issues — string padding, per-TU label
+The four link-composition issues — string alignment, per-TU label
namespacing, missing library mode, missing :ELF_end suppression —
-are now fixed in cc.scm itself (string padding is unconditional in
-`%cg-bv->hex-lines`; the other three are gated on the `--lib=PFX`
+are now fixed in cc.scm itself (string alignment is emitted with
+`.align 8`; the other three are gated on the `--lib=PFX`
flag, see §Linking). Remaining issues:
- **Empty-arg-list redecl rejection.** mes headers' K&R-style `f();`
diff --git a/tests/cc-cg/65-goto.scm b/tests/cc-cg/65-goto.scm
@@ -12,9 +12,8 @@
;; return s; /* 3 */
;; }
;;
-;; Exercises cg-emit-label (drops ::user_<name>) and cg-goto (emits
-;; %b(&::user_<name>)). Forward refs work because libp1pp's %scope
-;; resolves labels at emit time.
+;; Exercises cg-emit-label / cg-goto. C labels are function-qualified
+;; globals, so forward refs work even when loop/switch scopes are nested.
(let* ((cg (cg-init))
(params (cg-fn-begin cg "main" '() %t-i32))