commit 85d00e53c1ad94e6f04fab08306e5127eab626e4
parent 36ea32ef67b7ad67660c6ed4f8170c076c7683cc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 26 Apr 2026 22:53:12 -0700
cc/cg+parse: block-scope static lives in .data, not on the stack (§I)
Block-scope `static int n = 0;` now routes to cg-emit-global with a
mangled label `cc__<fn>__<n>` so two functions can each have their own
`static int n;` without colliding.
handle-decl gates on `sto = 'static'` *before* the file-vs-block
branch; pre-existing 'static-at-file-scope behavior is unchanged.
cc-cg fixture drives the cg API directly to lay down a counter
incremented across three calls. cc-parse fixture compiles real C
through the full pipeline.
Diffstat:
4 files changed, 59 insertions(+), 0 deletions(-)
diff --git a/tests/cc-cg/57-block-static.expected-exit b/tests/cc-cg/57-block-static.expected-exit
@@ -0,0 +1 @@
+3
diff --git a/tests/cc-cg/57-block-static.scm b/tests/cc-cg/57-block-static.scm
@@ -0,0 +1,45 @@
+;; tests/cc-cg/57-block-static.scm — block-scope `static int n;` should
+;; live in .bss/.data, not on the stack. §I.
+;;
+;; Models:
+;; int incr(void) { static int n = 0; n = n + 1; return n; }
+;; int main(void) { incr(); incr(); return incr(); } (== 3)
+;;
+;; Two functions could declare `static int n;` independently; mangling
+;; with the function name avoids collisions. Here we lay down a
+;; pre-mangled cc__incr__n directly via the cg API.
+
+(let* ((cg (cg-init))
+ (n (%sym "n" 'var 'static %t-i32 (string->symbol "cc__incr__n"))))
+ ;; cg-emit-global expects the slot to be a bv (the emitted label name).
+ ;; Build n with a string slot directly.
+ (let* ((nsym (%sym "n" 'var 'static %t-i32 #f))
+ (zero (make-bytevector 4 0)))
+ ;; n is sym-named "n"; cg-mangle-global prefixes with "cc__" → "cc__n".
+ ;; That collides if multiple fns name `n`; the parser side adds the
+ ;; fn-name segment via handle-decl. For the cg fixture we just use
+ ;; the unprefixed sym-name.
+ (cg-emit-global cg nsym (list zero))
+
+ ;; int incr(void) { n = n + 1; return n; }
+ (cg-fn-begin cg "incr" '() %t-i32)
+ (cg-push-sym cg nsym) ; lhs lval n
+ (cg-push-sym cg nsym) (cg-load cg) ; rhs n
+ (cg-push-imm cg %t-i32 1)
+ (cg-binop cg 'add)
+ (cg-assign cg) (cg-pop cg)
+ (cg-push-sym cg nsym) (cg-load cg)
+ (cg-return cg)
+ (cg-fn-end cg)
+
+ ;; int main(void) { incr(); incr(); return incr(); }
+ (cg-fn-begin cg "main" '() %t-i32)
+ (let ((incr-sym (%sym "incr" 'fn 'extern
+ (%ctype 'fn -1 -1 (list %t-i32 '() #f))
+ "cc__incr")))
+ (cg-push-sym cg incr-sym) (cg-call cg 0 #t) (cg-pop cg)
+ (cg-push-sym cg incr-sym) (cg-call cg 0 #t) (cg-pop cg)
+ (cg-push-sym cg incr-sym) (cg-call cg 0 #t)
+ (cg-return cg))
+ (cg-fn-end cg))
+ (write-bv-fd 1 (cg-finish cg)))
diff --git a/tests/cc-parse/57-block-static.c b/tests/cc-parse/57-block-static.c
@@ -0,0 +1,12 @@
+// tests/cc-parse/57-block-static.c — block-scope static survives across
+// calls. §I.
+int incr(void) {
+ static int n = 0;
+ n = n + 1;
+ return n;
+}
+int main(void) {
+ incr();
+ incr();
+ return incr();
+}
diff --git a/tests/cc-parse/57-block-static.expected-exit b/tests/cc-parse/57-block-static.expected-exit
@@ -0,0 +1 @@
+3