commit c788ee58d6d3019c89d929467fa7ebd8d1fad4e8
parent 21fd5f91cb8798e4a79b49ef028e8a307bf84e87
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 22 Jul 2026 17:20:10 -0700
Add Kit0 bootstrap compiler path
Diffstat:
42 files changed, 1584 insertions(+), 185 deletions(-)
diff --git a/M1pp/M1pp.P1 b/M1pp/M1pp.P1
@@ -13,7 +13,7 @@
## stream and walks it token-by-token, dispatching to
## define_macro at line-start %macro, emit_newline /
## emit_token for pass-through, expand_builtin_call for
-## !@%$, %select, %str, %local, and expand_call
+## !@%$, %select, %str, %local, %reset-output, and expand_call
## for user macros. Macro expansions and %select push
## fresh streams onto streams[]; popping rewinds the
## expansion pool.
@@ -1019,7 +1019,7 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
## B — second-byte dispatch: within the c0=='%' branch, a single
## c1-byte switch picks at most one directive/builtin to actually
## compare against (e.g. c1='s' selects %struct/%select/%str only).
-## A user macro %FOO with c1 outside {m,s,e,f,b,l} skips every
+## A user macro %FOO with c1 outside {m,s,e,f,b,l,r} skips every
## tok_eq_const and goes straight to find_macro.
:proc_loop
# s = current_stream(); if (s == 0) done
@@ -1132,6 +1132,9 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
li_a0 %108 %0
la_br &proc_c1_l
beq_a3,a0
+ li_a0 %114 %0
+ la_br &proc_c1_r
+ beq_a3,a0
# No directive/builtin candidate — try user macro.
la_br &proc_check_macro
b
@@ -1298,6 +1301,27 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
la_br &proc_do_builtin
b
+:proc_c1_r
+ # %reset-output BASE (no paren). Discard everything emitted by the
+ # macro/runtime prelude and seed a disjoint macro-hygiene id range.
+ ld_a0,sp,8
+ la_a1 &const_reset_output
+ li_a2 %13 %0
+ la_br &tok_eq_const
+ call
+ la_br &proc_check_macro
+ beqz_a0
+ ld_a0,sp,0
+ ld_a1,sp,8
+ la_br &proc_save_pos_and_ls
+ call
+ ld_a0,sp,0
+ ld_a0,a0,8
+ la_br &reset_output
+ call
+ la_br &proc_restore_and_loop
+ b
+
:proc_handle_newline
ld_a0,sp,0
ld_t0,sp,8
@@ -1516,6 +1540,68 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
:pop_frame_done
eret
+## reset_output(a0 = stream_end): consume a line-start `%reset-output BASE`,
+## clear the buffered output, and install BASE as next_expansion_id. The next
+## macro expansion receives BASE+1. This lets partitioned builds replay a
+## shared macro prelude without duplicating its runtime output or hygiene ids.
+:reset_output
+ enter_16
+ st_a0,sp,0
+
+ # Directive is valid only at line start.
+ la_t0 &proc_line_start
+ ld_t1,t0,0
+ la_br &err_bad_directive
+ beqz_t1
+
+ # Advance to BASE and require a WORD token within the stream.
+ la_t0 &proc_pos
+ ld_t1,t0,0
+ addi_t1,t1,32
+ st_t1,t0,0
+ ld_a0,sp,0
+ la_br &err_bad_directive
+ beq_t1,a0
+ ld_t2,t1,0
+ la_br &err_bad_directive
+ bnez_t2
+
+ # parse_int_token(BASE), accepting 0..INT_MAX-1.
+ mov_a0,t1
+ la_br &parse_int_token
+ call
+ la_br &err_bad_directive
+ bltz_a0
+ li_t0 %2147483646 %0
+ la_br &err_bad_directive
+ blt_t0,a0
+ la_t0 &next_expansion_id
+ st_a0,t0,0
+
+ # Require and consume the newline immediately following BASE.
+ la_t0 &proc_pos
+ ld_t1,t0,0
+ addi_t1,t1,32
+ st_t1,t0,0
+ ld_a0,sp,0
+ la_br &err_bad_directive
+ beq_t1,a0
+ ld_t2,t1,0
+ li_t0 TOK_NEWLINE
+ la_br &err_bad_directive
+ bne_t2,t0
+ addi_t1,t1,32
+ la_t0 &proc_pos
+ st_t1,t0,0
+
+ # Drop the replayed prelude's output and reset token spacing.
+ li_t0 %0 %0
+ la_t1 &output_used
+ st_t0,t1,0
+ la_t1 &output_need_space
+ st_t0,t1,0
+ eret
+
## --- %macro storage: parse header + body into macros[] / macro_body_tokens --
## Called at proc_pos == line-start `%macro`. Leaves proc_pos past the %endm
## line with proc_line_start = 1. Uses BSS scratch (def_m_ptr, def_param_ptr,
@@ -6557,6 +6643,7 @@ DEFINE OFF_macro_body_is_local_label 80632A1F00000000
:const_endframe "%endframe"
:const_local "%local"
:const_bytes "%bytes"
+:const_reset_output "%reset-output"
## Suffix appended to the frame name when looking up <frame>_FRAME.<field>.
:const_frame_suffix "_FRAME."
diff --git a/M1pp/M1pp.c b/M1pp/M1pp.c
@@ -10,6 +10,8 @@
*
* %struct NAME { f1 f2 ... } fixed-layout 8-byte-field aggregate
* %enum NAME { l1 l2 ... } incrementing integer constants
+ * %reset-output BASE discard output so far and seed the
+ * macro-hygiene expansion id
*
* %NAME(x, y) function-like macro call
* ## token pasting inside macro bodies
@@ -1913,6 +1915,30 @@ static int pop_frame(struct Stream *s)
return 1;
}
+static int reset_output(struct Stream *s)
+{
+ long long base;
+
+ if (!s->line_start) {
+ return fail("bad reset-output directive");
+ }
+ s->pos++;
+ if (s->pos >= s->end || s->pos->kind != TOK_WORD ||
+ !parse_int_token(s->pos, &base) || base < 0 || base > 2147483646LL) {
+ return fail("bad reset-output directive");
+ }
+ s->pos++;
+ if (s->pos >= s->end || s->pos->kind != TOK_NEWLINE) {
+ return fail("bad reset-output directive");
+ }
+ s->pos++;
+ s->line_start = 1;
+ output_used = 0;
+ output_need_space = 0;
+ next_expansion_id = (int)base;
+ return 1;
+}
+
static int process_tokens(void)
{
if (!push_stream_span((struct TokenSpan){source_tokens, source_tokens + source_count}, -1)) {
@@ -2016,6 +2042,13 @@ static int process_tokens(void)
handled = 1;
}
break;
+ case 'r':
+ if (len == 13 &&
+ memcmp(p + 2, "eset-output", 11) == 0) {
+ if (!reset_output(s)) return 0;
+ handled = 1;
+ }
+ break;
}
if (handled) {
diff --git a/Makefile b/Makefile
@@ -68,7 +68,7 @@ OUT_DIR := build/$(ARCH)/$(DRIVER)
.SUFFIXES:
-.PHONY: all help clean cloc src package release
+.PHONY: all help clean cloc src package release kit0
# ── Top-level targets ────────────────────────────────────────────────────
@@ -98,10 +98,27 @@ help:
@echo ' assemble final static toolchain'
@echo ' make build/<arch>/<driver>/bootN/<file> any single artifact'
@echo ' make test SUITE=<suite> test suite (NAMES=<filter> optional)'
+ @echo ' make kit0 KIT0_PREP=<kit-prep-dir> cc.scm + P1 build of Kit0 for ARCH'
@echo ' make image per-arch tests container image'
@echo ' make cloc line counts for bootstrap sources'
@echo ' make clean rm -rf build/'
+KIT0_ARCHES := aarch64 amd64 riscv64
+KIT0_PROFILE_aarch64 := aa64
+KIT0_PROFILE_amd64 := x64
+KIT0_PROFILE_riscv64 := rv64
+KIT0_PROFILE ?= $(KIT0_PROFILE_$(ARCH))
+KIT_DIR ?= ../../kit
+KIT0_PREP ?= $(KIT_DIR)/build/bootstrap-$(KIT0_PROFILE)/kit0-prep
+
+ifeq ($(filter $(ARCH),$(KIT0_ARCHES)),)
+kit0:
+ @echo "kit0 supports ARCH in {$(KIT0_ARCHES)} (got '$(ARCH)')" >&2; exit 2
+else
+kit0: build/$(ARCH)/$(DRIVER)/boot2/.stamp build/$(ARCH)/src/.stamp
+ KIT0_PREP='$(KIT0_PREP)' DRIVER='$(DRIVER)' boot/kit0.sh $(ARCH)
+endif
+
clean:
rm -rf build/
diff --git a/P1/P1-aarch64.M1pp b/P1/P1-aarch64.M1pp
@@ -412,6 +412,9 @@
%macro aa64_rrr_DIV(rd, ra, rb)
%aa64_rrr(0x9AC00C00, rd, ra, rb)
%endm
+%macro aa64_rrr_UDIV(rd, ra, rb)
+%aa64_rrr(0x9AC00800, rd, ra, rb)
+%endm
%macro aa64_rrr_MUL(rd, ra, rb)
%((| 0x9B000000 (<< %aa64_reg(rb) 16) (<< 31 10) (<< %aa64_reg(ra) 5) %aa64_reg(rd)))
%endm
@@ -419,6 +422,10 @@
%((| 0x9AC00C00 (<< %aa64_reg(rb) 16) (<< %aa64_reg(ra) 5) %aa64_reg(scratch)))
%((| 0x9B008000 (<< %aa64_reg(rb) 16) (<< %aa64_reg(ra) 10) (<< %aa64_reg(scratch) 5) %aa64_reg(rd)))
%endm
+%macro aa64_rrr_UREM(rd, ra, rb)
+%((| 0x9AC00800 (<< %aa64_reg(rb) 16) (<< %aa64_reg(ra) 5) %aa64_reg(scratch)))
+%((| 0x9B008000 (<< %aa64_reg(rb) 16) (<< %aa64_reg(ra) 10) (<< %aa64_reg(scratch) 5) %aa64_reg(rd)))
+%endm
%macro aa64_rrr_op(op, rd, ra, rb)
%aa64_rrr_##op(rd, ra, rb)
diff --git a/P1/P1-amd64.M1pp b/P1/P1-amd64.M1pp
@@ -327,6 +327,13 @@
%amd_modrm_ext_r(7, src)
%endm
+# div src -- 48 F7 /6.
+%macro amd_div_r(src)
+%amd_rex_wb(src)
+!(0xF7)
+%amd_modrm_ext_r(6, src)
+%endm
+
# cqo -- 48 99 (sign-extend rax into rdx:rax).
%macro amd_cqo()
!(0x48)
@@ -529,6 +536,27 @@
%amd_mov_rr(rd, rax)
%endm
+%macro amd_rrr_UDIV(rd, ra, rb)
+%amd_mov_rr(rbp, rdx)
+%amd_mov_rr(scratch, rb)
+%amd_mov_rr(rax, ra)
+%amd_alu_rr(0x31, rdx, rdx)
+%amd_div_r(scratch)
+%amd_mov_rr(rdx, rbp)
+%amd_mov_rr(rd, rax)
+%endm
+
+%macro amd_rrr_UREM(rd, ra, rb)
+%amd_mov_rr(rbp, rdx)
+%amd_mov_rr(scratch, rb)
+%amd_mov_rr(rax, ra)
+%amd_alu_rr(0x31, rdx, rdx)
+%amd_div_r(scratch)
+%amd_mov_rr(rax, rdx)
+%amd_mov_rr(rdx, rbp)
+%amd_mov_rr(rd, rax)
+%endm
+
# SHL / SHR / SAR with reg count. x86 reads the count from CL only, so
# staging goes through rcx — which IS P1 a3. Save rcx to rbp for the
# duration.
diff --git a/P1/P1-riscv32.M1pp b/P1/P1-riscv32.M1pp
@@ -385,9 +385,15 @@
%macro rv_rrr_DIV(rd, ra, rb)
%rv_r_type(0x02004033, rd, ra, rb)
%endm
+%macro rv_rrr_UDIV(rd, ra, rb)
+%rv_r_type(0x02005033, rd, ra, rb)
+%endm
%macro rv_rrr_REM(rd, ra, rb)
%rv_r_type(0x02006033, rd, ra, rb)
%endm
+%macro rv_rrr_UREM(rd, ra, rb)
+%rv_r_type(0x02007033, rd, ra, rb)
+%endm
%macro rv_rrr_op(op, rd, ra, rb)
%rv_rrr_##op(rd, ra, rb)
diff --git a/P1/P1-riscv64.M1pp b/P1/P1-riscv64.M1pp
@@ -403,9 +403,15 @@
%macro rv_rrr_DIV(rd, ra, rb)
%rv_r_type(0x02004033, rd, ra, rb)
%endm
+%macro rv_rrr_UDIV(rd, ra, rb)
+%rv_r_type(0x02005033, rd, ra, rb)
+%endm
%macro rv_rrr_REM(rd, ra, rb)
%rv_r_type(0x02006033, rd, ra, rb)
%endm
+%macro rv_rrr_UREM(rd, ra, rb)
+%rv_r_type(0x02007033, rd, ra, rb)
+%endm
%macro rv_rrr_op(op, rd, ra, rb)
%rv_rrr_##op(rd, ra, rb)
diff --git a/P1/P1.M1pp b/P1/P1.M1pp
@@ -66,10 +66,18 @@ target
%p1_rrr(DIV, rd, ra, rb)
%endm
+%macro udiv(rd, ra, rb)
+%p1_rrr(UDIV, rd, ra, rb)
+%endm
+
%macro rem(rd, ra, rb)
%p1_rrr(REM, rd, ra, rb)
%endm
+%macro urem(rd, ra, rb)
+%p1_rrr(UREM, rd, ra, rb)
+%endm
+
# ---- Immediate arithmetic -------------------------------------------------
%macro addi(rd, ra, imm)
diff --git a/boot/kit0.sh b/boot/kit0.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+## Compile a prepared Kit0 translation-unit graph through cc.scm, then use the
+## ordinary per-architecture P1/M1pp/hex2pp boot path to produce one executable.
+set -eu
+
+. boot/lib-arch.sh
+bootlib_init kit0 "${1:-aarch64}"
+
+case "$ARCH" in
+ aarch64) kit_arch_flag=KIT_ARCH_AA64_ENABLED ;;
+ amd64) kit_arch_flag=KIT_ARCH_X64_ENABLED ;;
+ riscv64) kit_arch_flag=KIT_ARCH_RV64_ENABLED ;;
+ *)
+ echo "kit0: supported architectures are aarch64, amd64, and riscv64" >&2
+ exit 2
+ ;;
+esac
+
+: "${KIT0_PREP:?set KIT0_PREP to the matching Kit bootstrap profile kit0-prep directory}"
+test -f "$KIT0_PREP/manifest.tsv"
+test -f "$KIT0_PREP/m1pp-partitions.tsv"
+test -f "$KIT0_PREP/kit0-run.scm"
+test -f "$KIT0_PREP/resolved-config.h"
+test -d "$KIT0_PREP/tu"
+grep -Eq "^#define[[:space:]]+${kit_arch_flag}[[:space:]]+1$" \
+ "$KIT0_PREP/resolved-config.h" || {
+ echo "kit0: $KIT0_PREP does not enable $kit_arch_flag for ARCH=$ARCH" >&2
+ exit 2
+}
+
+driver_init empty
+require_src
+
+BOOT1=build/$ARCH/$DRIVER/boot1
+BOOT2=build/$ARCH/$DRIVER/boot2
+SRC=build/$ARCH/src
+OUT=build/$ARCH/$DRIVER/kit0
+STAGE=build/$ARCH/.stage/$DRIVER/kit0
+
+require_prev "$BOOT1" M1pp hex2pp
+require_prev "$BOOT2" catm scheme1
+
+. boot/lib-runscm.sh
+runscm_init "$STAGE" "$OUT"
+runscm_scheme1 "$BOOT2/scheme1"
+runscm_prelude "$SRC/src/scheme1/prelude.scm"
+runscm_runscm "$KIT0_PREP/kit0-run.scm"
+
+runscm_input catm "$BOOT2/catm"
+runscm_input M1pp "$BOOT1/M1pp"
+runscm_input hex2pp "$BOOT1/hex2pp"
+runscm_input_from_src scheme1/prelude.scm
+runscm_input_from_src cc/cc.scm
+runscm_input_from_src cc/main.scm
+runscm_input_from_src "P1/P1-$ARCH.M1pp" backend.M1pp
+runscm_input_from_src P1/P1.M1pp frontend.M1pp
+runscm_input_from_src P1/P1pp.P1pp libp1pp.P1pp
+runscm_input_from_src P1/entry-libc.P1pp
+runscm_input_from_src P1/elf-end.P1pp
+runscm_input_from_src stage0-posix/ELF.hex2
+runscm_input_from_src libc/libc.flat.c
+runscm_input_tree kit0 "$KIT0_PREP/tu"
+
+runscm_export kit0 libc.P1pp
+# cc.scm processes the deliberately separate Kit translation units in one
+# deterministic seed run. On an emulated target this can take several hours.
+runscm_run "${KIT0_TIMEOUT:-28800}"
+
+echo "[$BOOT_TAG] OK -> $OUT/kit0 ($(wc -c <"$OUT/kit0") bytes)"
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -550,8 +550,8 @@
;; across functions (reset, not re-allocated).
;; --------------------------------------------------------------------
-;; Tuning constants — total fixed pre-allocation ≈ 12.27 MiB on a
-;; managed heap. Bump these when a workload overflows; the buf-overflow
+;; Tuning constants — total fixed pre-allocation is about 12.58 MiB on
+;; 64-bit targets and 12.33 MiB on RV32. Bump these when a workload overflows; the buf-overflow
;; die() reports off/len/cap so misses are easy to diagnose.
;;
;; Each cap is a power of two. scheme1's bv_capacity_for rounds the
@@ -560,7 +560,8 @@
(define %BUF-CAP-TEXT 8388608) ; 8 MiB: .text + entry stub
(define %BUF-CAP-DATA 2097152) ; 2 MiB: .data (strings, globals)
(define %BUF-CAP-BSS 2097152) ; 2 MiB: .bss
-(define %BUF-CAP-FN 262144) ; 256 KiB: per-fn body asm
+(define %BUF-CAP-FN (if (= (target-word-bytes) 4) 262144 524288))
+ ; 256/512 KiB: per-fn body asm
(define %BUF-CAP-PROLOGUE 16384) ; 16 KiB: per-fn prologue
(define %BUF-CAP-DEFAULT 65536) ; 64 KiB: make-buf fallback
@@ -902,13 +903,17 @@
;; fn-ctx — current-function context inside the parser.
;; --------------------------------------------------------------------
(define-record-type fn-ctx
- (%fn-ctx name return-type params variadic? labels)
+ (%fn-ctx name return-type params variadic? labels static-counter)
fn-ctx?
(name fn-ctx-name)
(return-type fn-ctx-return-type)
(params fn-ctx-params)
(variadic? fn-ctx-variadic?)
- (labels fn-ctx-labels fn-ctx-labels-set!))
+ (labels fn-ctx-labels fn-ctx-labels-set!)
+ ;; Monotonic declaration identity for block-scope static objects. The C
+ ;; identifier alone is not unique: separate lexical scopes in one function
+ ;; may each declare `static ... order[]` and must emit distinct labels.
+ (static-counter fn-ctx-static-counter fn-ctx-static-counter-set!))
;; --------------------------------------------------------------------
;; world — cross-decl persistent parser/cg state. The same world record
@@ -1042,7 +1047,7 @@
("extern" . extern) ("typedef" . typedef)
;; qualifiers (parsed and discarded by parse)
("const" . const) ("volatile" . volatile) ("restrict" . restrict)
- ("inline" . inline)
+ ("inline" . inline) ("_Noreturn" . _Noreturn)
;; GNU attribute spec — parsed and discarded; see skip-gnu-attribute!
("__attribute__" . __attribute__)
;; type specifiers
@@ -1064,7 +1069,8 @@
;; reserved-and-rejected (KW so diagnostics are crisp)
("_Generic" . _Generic) ("_Atomic" . _Atomic)
("_Thread_local" . _Thread_local)
- ("_Alignof" . _Alignof) ("_Alignas" . _Alignas)
+ ("_Alignof" . _Alignof) ("__alignof" . _Alignof)
+ ("__alignof__" . _Alignof) ("_Alignas" . _Alignas)
("_Static_assert" . _Static_assert)
("_Complex" . _Complex) ("_Imaginary" . _Imaginary)))
@@ -3095,11 +3101,11 @@
(if (< (cg-max-outgoing cg) n) (cg-max-outgoing-set! cg n) 0))
(define (%cg-slot-expr cg logical-off)
- (let ((nm (%cg-fn-get cg '%fn-name)))
+ (let ((nm (%cg-fn-get cg '%fn-label)))
(bv-cat (list "(+ %" nm "__SO " (%n logical-off) ")"))))
-(define (%cg-mangle-global name-bv)
- (bytevector-append "cc__" name-bv))
+(define (%cg-mangle-global cg name-bv)
+ (bytevector-append (cg-str-prefix cg) "cc__" name-bv))
;; Label for a sym at the M1 layer.
;;
@@ -3115,9 +3121,9 @@
;; Block-scope statics already mangle their sym-name to
;; `<fnname>__<n>` at parse time (see line ~5125); the cc__ prefix
;; here just nests another layer of namespacing on top of that.
-(define (%cg-sym-label sm)
+(define (%cg-sym-label cg sm)
(cond
- ((eq? (sym-storage sm) 'static) (%cg-mangle-global (sym-name sm)))
+ ((eq? (sym-storage sm) 'static) (%cg-mangle-global cg (sym-name sm)))
(else (sym-name sm))))
(define (%cg-reg->bv r) (bytevector-append (symbol->string r)))
@@ -3463,15 +3469,16 @@
(define (cg-fn-begin cg name params return-type)
(cg-fn-begin/v cg name params return-type #f))
-;; Variadic-aware variant. variadic? = #t reserves 16 contiguous target-word
-;; slots covering incoming arg indices 0..15, populating each from the
+;; Variadic-aware variant. variadic? = #t reserves a fixed contiguous
+;; target-word window, populating each slot from the
;; appropriate source: a-register for idx 0..3, LDARG slot (idx-4) for
-;; idx 4..15. va_start computes the address of the slot at index =
+;; later indices. va_start computes the address of the slot at index =
;; named-arg count, so va_arg walks linearly through the rest.
-;; Indices 4..15 may be garbage when the caller passed fewer args; user
+;; Indices beyond the actual arguments may be garbage; user
;; code stops walking based on a count or sentinel before those slots
-;; are read. Limit of 15 variadic args (after named) is enough for
-;; tcc.c's logging shapes; bump VARARG_WINDOW if you need more.
+;; are read. Kit's widest diagnostic calls require 20 total arguments.
+(define %CG-VARARG-WINDOW 32)
+
(define (cg-fn-begin/v cg name params return-type variadic?)
(buf-reset! (cg-fn-buf cg))
(buf-reset! (cg-prologue-buf cg))
@@ -3487,6 +3494,9 @@
(cg-max-outgoing-set! cg 0)
(cg-fn-meta-set! cg '())
(%cg-fn-set! cg '%fn-name name)
+ (let ((sm (%hash-ref (car (world-scope (cg-world cg))) name)))
+ (%cg-fn-set! cg '%fn-label
+ (cond (sm (%cg-sym-label cg sm)) (else name))))
(%cg-fn-set! cg '%fn-ret-type return-type)
(%cg-fn-set! cg '%indirect-slots '())
(%cg-fn-set! cg '%fn-variadic? variadic?)
@@ -3524,13 +3534,18 @@
(bv-cat (list "%st(a0, sp, "
(%cg-slot-expr cg ss) ")\n")))))
(else (%cg-fn-set! cg '%fn-sret-slot #f))))
- ;; Variadic save area is capped at 16 incoming-arg slots; reject
+ ;; Reject definitions whose named parameters leave no variadic slot.
;; variadic definitions whose named-arg count would already fill or
;; exceed it (no room left for variadic reads).
- (cond
- ((and variadic? (> (length params) 16))
- (die #f "cg-fn-begin: variadic function exceeds 16-arg save-area cap"
- name (length params))))
+ (let ((named-slots
+ (let count ((xs params) (n 0))
+ (cond ((null? xs) n)
+ (else (count (cdr xs)
+ (+ n (%cg-param-reg-count (cdar xs)))))))))
+ (cond
+ ((and variadic? (>= named-slots %CG-VARARG-WINDOW))
+ (die #f "cg-fn-begin: variadic function fills save-area"
+ name named-slots %CG-VARARG-WINDOW))))
;; With sret, explicit arg i lives at ABI position (i+1): args 0..2
;; in a1..a3, args 3+ in slot (i-3).
(let* ((sret-shift (if (%cg-fn-get cg '%fn-sret?) 1 0))
@@ -3553,7 +3568,7 @@
(variadic?
(let pad ((i idx) (vfirst #f) (fs first-slot))
(cond
- ((>= i 16)
+ ((>= i %CG-VARARG-WINDOW)
(%cg-fn-set! cg '%fn-vararg-first-slot (or vfirst fs))
(reverse out))
(else
@@ -3565,16 +3580,17 @@
(let* ((p (car ps))
(nm (car p))
(ty (cdr p))
- ;; Two-word aggregates ride two consecutive arg positions;
- ;; wider aggregates
- ;; aggregates would normally pass by reference — not
- ;; supported here yet.
+ ;; Aggregates wider than two words ride one ABI slot containing
+ ;; a pointer to a caller-owned copy (AAPCS64 / SysV class
+ ;; MEMORY). Smaller aggregates ride one or two words inline.
(n (%cg-param-reg-count ty))
- (sz (cond ((or (%cg-param-aggregate? ty)
+ (sz (cond ((%cg-param-indirect? ty) %CC-WORD-BYTES)
+ ((or (%cg-param-aggregate? ty)
(%ctype-wide-int? ty))
(align-up (ctype-size ty) %CC-WORD-BYTES))
(else %CC-WORD-BYTES)))
- (al (cond ((or (%cg-param-aggregate? ty)
+ (al (cond ((%cg-param-indirect? ty) %CC-WORD-BYTES)
+ ((or (%cg-param-aggregate? ty)
(%ctype-wide-int? ty))
(max %CC-WORD-BYTES (ctype-align ty)))
(else %CC-WORD-BYTES)))
@@ -3586,6 +3602,7 @@
(spill (+ idx sret-shift i)
(+ off (* i %CC-WORD-BYTES)))
(chunk (+ i 1)))))
+ (cond ((%cg-param-indirect? ty) (%cg-mark-indirect! cg off)))
(walk (cdr ps) (+ idx n) (cons (cons nm psym) out)
(or first-slot off))))))))
@@ -3597,8 +3614,7 @@
((%cg-param-aggregate? ty)
(let ((sz (ctype-size ty)))
(cond
- ((> sz %CC-PAIR-BYTES)
- (die #f "cg: aggregate arg/param wider than two words not supported" sz))
+ ((> sz %CC-PAIR-BYTES) 1)
((> sz %CC-WORD-BYTES) 2)
(else 1))))
(else 1)))
@@ -3607,6 +3623,10 @@
(let ((k (ctype-kind ty)))
(or (eq? k 'struct) (eq? k 'union))))
+(define (%cg-param-indirect? ty)
+ (and (%cg-param-aggregate? ty)
+ (> (ctype-size ty) %CC-PAIR-BYTES)))
+
(define (cg-fn-end cg)
;; Drain prologue-buf and fn-buf directly into cg-text via buf-drain!
;; (memcpy, no allocation). Header/footer pieces go through buf-push!
@@ -3623,9 +3643,7 @@
;; emits the same cc__-mangled label that callers reference.
;; The sym was bound by parse-fn-body before the body parse,
;; so it's in the top scope frame at this point.
- (fn-sym (%hash-ref (car (world-scope (cg-world cg))) name))
- (mangled (cond (fn-sym (%cg-sym-label fn-sym))
- (else name)))
+ (mangled (%cg-fn-get cg '%fn-label))
(tb (cg-text cg)))
;; Now that the body is fully emitted, leave fn dispatch so any
;; trailing emits in this function (including the ret-block below)
@@ -3633,7 +3651,7 @@
(cg-in-fn?-set! cg #f)
;; staging-size macro
(buf-push! tb "%macro ")
- (buf-push! tb name)
+ (buf-push! tb mangled)
(buf-push! tb "__SO()\n")
(buf-push! tb (%n staging-bytes))
(buf-push! tb "\n%endm\n")
@@ -3718,7 +3736,11 @@
;; bytes past the new offset become garbage that the next buf-push!
;; will overwrite (buf-push! always copies into [offset, offset+len)).
;; frame-hi and max-outgoing are also restored so cg-alloc-slot calls
-;; inside the rewound region don't leak frame bytes.
+;; inside the rewound region don't leak frame bytes. The label counter and
+;; indirect-slot set are part of the same transaction: parsing `sizeof *p`
+;; mints an indirect temporary, and leaving that tag behind after frame-hi is
+;; rewound would make the next ordinary local at the reused offset behave as a
+;; pointer-to-storage slot.
;; --------------------------------------------------------------------
(define (cg-snapshot cg)
(cond
@@ -3727,13 +3749,17 @@
(list (cg-vstack cg)
(buf-offset (cg-fn-buf cg))
(cg-frame-hi cg)
- (cg-max-outgoing cg)))
+ (cg-max-outgoing cg)
+ (cg-label-ctr cg)
+ (or (%cg-fn-get cg '%indirect-slots) '())))
(define (cg-rewind cg tag)
(cg-vstack-set! cg (car tag))
(buf-offset-set! (cg-fn-buf cg) (cadr tag))
(cg-frame-hi-set! cg (caddr tag))
- (cg-max-outgoing-set! cg (cadddr tag)))
+ (cg-max-outgoing-set! cg (cadddr tag))
+ (cg-label-ctr-set! cg (car (cddddr tag)))
+ (%cg-fn-set! cg '%indirect-slots (cadr (cddddr tag))))
;; Duplicate the top vstack entry. For lvals this is safe — the slot
;; (or label, or indirect-marked frame) backing the lval keeps existing
@@ -3759,13 +3785,13 @@
(define (cg-push-sym cg sm)
(pmatch sm
(($ sym? (kind fn) (type ,ty))
- (cg-push cg (%opnd 'global ty (%cg-sym-label sm) #f)))
+ (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #f)))
(($ sym? (kind enum-const) (type ,ty) (slot ,v))
(cg-push cg (%opnd 'imm ty v #f)))
(($ sym? (kind var) (storage extern) (type ,ty))
- (cg-push cg (%opnd 'global ty (%cg-sym-label sm) #t)))
+ (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #t)))
(($ sym? (kind var) (storage static) (type ,ty))
- (cg-push cg (%opnd 'global ty (%cg-sym-label sm) #t)))
+ (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #t)))
(($ sym? (kind var) (type ,ty) (slot ,off))
(cg-push cg (%opnd 'frame ty off #t)))
(($ sym? (kind param) (type ,ty) (slot ,off))
@@ -4327,8 +4353,10 @@
(if (%ctype-unsigned? ta)
(%cg-emit-rrr cg "shr" 't0 'a0 'a1)
(%cg-emit-rrr cg "sar" 't0 'a0 'a1)))
- ((eq? op 'div) (%cg-emit-rrr cg "div" 't0 'a0 'a1))
- ((eq? op 'rem) (%cg-emit-rrr cg "rem" 't0 'a0 'a1))
+ ((eq? op 'div)
+ (%cg-emit-rrr cg (if unsigned? "udiv" "div") 't0 'a0 'a1))
+ ((eq? op 'rem)
+ (%cg-emit-rrr cg (if unsigned? "urem" "rem") 't0 'a0 'a1))
((eq? op 'eq) (%cg-emit-cmp cg "eq" 'a0 'a1 't0))
((eq? op 'ne) (%cg-emit-cmp cg "ne" 'a0 'a1 't0))
((eq? op 'lt) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a0 'a1 't0))
@@ -4452,7 +4480,7 @@
(or (eq? rk 'struct) (eq? rk 'union))
(> (ctype-size rty) %CC-PAIR-BYTES)))
;; If the callee is variadic, the callee's save area caps total
- ;; incoming args at 16. Reject silent miscompiles up front.
+ ;; incoming ABI words. Reject silent miscompiles up front.
(callee-fty (cond
((eq? (ctype-kind fty) 'fn) fty)
((and (eq? (ctype-kind fty) 'ptr)
@@ -4464,10 +4492,17 @@
(and (pair? ext) (pair? (cdr ext))
(pair? (cddr ext))
(car (cddr ext))))))
+ (arg-slots
+ (let count ((xs args) (n 0))
+ (cond ((null? xs) n)
+ (else (count (cdr xs)
+ (+ n (%cg-param-reg-count
+ (opnd-type (car xs)))))))))
(_cap-check (cond
- ((and callee-variadic? (> arity 16))
- (die #f "cg-call: variadic call exceeds 16-arg save-area cap"
- arity))
+ ((and callee-variadic?
+ (> arg-slots %CG-VARARG-WINDOW))
+ (die #f "cg-call: variadic call exceeds save-area"
+ arg-slots %CG-VARARG-WINDOW))
(else 0)))
(sret-shift (if sret? 1 0))
(recv-slot (cond
@@ -4476,14 +4511,53 @@
(align-up (ctype-size rty) %CC-WORD-BYTES)
(max %CC-WORD-BYTES (ctype-align rty))))
(else #f))))
- (let stage ((xs args) (idx 0))
- (cond
- ((null? xs) 0)
- (else
- (let* ((arg (car xs))
- (aty (opnd-type arg))
- (n (%cg-param-reg-count aty)))
- (cond
+ ;; Copy every indirect aggregate before loading any ABI argument register:
+ ;; %memcpy_call uses a0-a2, so interleaving a later copy with register
+ ;; staging would clobber earlier arguments. The parallel list records the
+ ;; private frame slot for each large aggregate and #f for direct args.
+ (let ((indirect-copies
+ (let materialize ((xs args) (out '()))
+ (cond
+ ((null? xs) (reverse out))
+ (else
+ (let* ((arg (car xs))
+ (aty (opnd-type arg)))
+ (cond
+ ((%cg-param-indirect? aty)
+ (let* ((sz (ctype-size aty))
+ (al (max %CC-WORD-BYTES (ctype-align aty)))
+ (slot (cg-alloc-slot
+ cg (align-up sz %CC-WORD-BYTES) al)))
+ (%cg-emit-addr-of cg arg 't0)
+ (%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg slot))
+ (%cg-emit-byte-copy cg 't2 't0 't1 sz)
+ (materialize (cdr xs) (cons slot out))))
+ (else
+ (materialize (cdr xs) (cons #f out))))))))))
+ (let stage ((xs args) (copies indirect-copies) (idx 0))
+ (cond
+ ((null? xs) 0)
+ (else
+ (let* ((arg (car xs))
+ (aty (opnd-type arg))
+ (n (%cg-param-reg-count aty)))
+ (cond
+ ;; Large aggregate: pass the address of the private copy through
+ ;; one pointer ABI slot. The callee marks its pointer slot
+ ;; indirect, so ordinary struct lvalue operations address it.
+ ((%cg-param-indirect? aty)
+ (let ((slot (car copies))
+ (abi (+ idx sret-shift)))
+ (cond
+ ((< abi 4)
+ (%cg-emit-lea-slot cg
+ (%cg-reg->bv (%reg-by-idx abi))
+ (%cg-slot-expr cg slot)))
+ (else
+ (%cg-emit-lea-slot cg "t0" (%cg-slot-expr cg slot))
+ (%cg-emit-st cg 't0 'sp
+ (* %CC-WORD-BYTES (- abi 4)))))
+ (stage (cdr xs) (cdr copies) (+ idx 1))))
;; RV32 i64/u64 values consume two consecutive ABI words.
((%ctype-wide-int? aty)
(%cg-load-wide-opnd-into cg arg 't0 't1)
@@ -4504,11 +4578,14 @@
(%cg-emit-st cg src 'sp
(* %CC-WORD-BYTES (- tabi 4)))))
(chunk (+ i 1))))))
- (stage (cdr xs) (+ idx 2)))
- ;; Multi-word aggregate: load its target-word chunks into successive arg
- ;; regs / stack slots. Stage the struct's address in t0
- ;; once and chunk-load one target word at a time.
- ((and (%cg-param-aggregate? aty) (> n 1))
+ (stage (cdr xs) (cdr copies) (+ idx 2)))
+ ;; Direct aggregate: load its target-word chunks into successive
+ ;; arg regs / stack slots. This path also handles a one-word
+ ;; aggregate: unlike the generic scalar load below, addr-of knows
+ ;; when an aggregate lvalue is represented by an indirect frame
+ ;; slot (for example `p->loc`) and follows that slot before the
+ ;; chunk load.
+ ((%cg-param-aggregate? aty)
(%cg-emit-addr-of cg arg 't0)
(let chunk ((i 0))
(cond
@@ -4529,18 +4606,18 @@
(%cg-emit-st cg 't1 'sp
(* %CC-WORD-BYTES (- tabi 4))))))
(chunk (+ i 1)))))
- (stage (cdr xs) (+ idx n)))
+ (stage (cdr xs) (cdr copies) (+ idx n)))
(else
(let ((abi (+ idx sret-shift)))
(cond
((< abi 4)
(%cg-load-opnd-into cg arg (%reg-by-idx abi))
- (stage (cdr xs) (+ idx 1)))
+ (stage (cdr xs) (cdr copies) (+ idx 1)))
(else
(%cg-load-opnd-into cg arg 't0)
(%cg-emit-st cg 't0 'sp
(* %CC-WORD-BYTES (- abi 4)))
- (stage (cdr xs) (+ idx 1)))))))))))
+ (stage (cdr xs) (cdr copies) (+ idx 1))))))))))))
;; Stack-arg footprint accounts for the extra ABI slot any
;; >8B-aggregate arg consumed beyond its single-position cousin.
(let* ((nabi (let count ((xs args) (n sret-shift))
@@ -4768,10 +4845,10 @@
(%cg-emit-many cg (list "%continue\n")))
;; --------------------------------------------------------------------
-;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a 16-slot
-;; (one target word each) save area at known frame offsets, populating each
+;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a fixed
+;; target-word save area at known frame offsets, populating each
;; slot from the appropriate ABI source — a-register for indices 0..3,
-;; LDARG for indices 4..15. va_start sets ap to the address of the
+;; LDARG for later indices. va_start sets ap to the address of the
;; first slot past the named-arg count; va_arg reads *ap, advances ap
;; by one target word, and pushes the value as the requested type.
;;
@@ -4780,8 +4857,8 @@
;; pushes nothing. cg-va-arg pops ap-lval, loads ap, dereferences for
;; the value, advances ap, stores back, pushes the loaded value.
;;
-;; Cap: total incoming args (named + variadic) must fit in the 16-slot
-;; save area. Variadic call sites exceeding this die in cg-call;
+;; Cap: total incoming ABI words (named + variadic) must fit in the
+;; %CG-VARARG-WINDOW-slot save area. Variadic call sites exceeding this die in cg-call;
;; variadic definitions whose named-arg count exceeds it die in
;; cg-fn-begin/v.
;; --------------------------------------------------------------------
@@ -4843,8 +4920,8 @@
;; 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))))
+ (let ((fn (%cg-fn-get cg '%fn-label)))
+ (bv-cat (list fn "__user_" name-bv))))
(define (cg-emit-label cg name-bv)
(%cg-emit-many cg (list ":" (%cg-user-label cg name-bv) "\n")))
@@ -4959,7 +5036,7 @@
(else (die #f "cg-emit-global: bad init piece" piece))))
(define (cg-emit-global cg sym init)
- (let* ((lbl (%cg-sym-label sym))
+ (let* ((lbl (%cg-sym-label cg sym))
(sz (ctype-size (sym-type sym)))
(size (if (< sz 0) %CC-WORD-BYTES sz))
(al (max 1 (ctype-align (sym-type sym)))))
@@ -5211,6 +5288,8 @@
(else
(let ((v (%hash-ref (car f) n)))
(if v v (loop (cdr f))))))))
+(define (scope-lookup-current ps n)
+ (%hash-ref (car (ps-scope ps)) n))
(define (tag-bind! ps n c)
(%hash-set! (car (ps-tags ps)) n c))
(define (tag-lookup ps n)
@@ -5265,6 +5344,31 @@
(skip-gnu-attribute! ps) (eat-gnu-attributes! ps))
(else #t)))
+;; Parse the universally layout-neutral C11 alignment requests: zero (the
+;; standard's no-effect spelling) and one/char alignment (which cannot weaken
+;; any object's natural alignment). cc.scm does not carry a pending alignment
+;; specifier into its declarator layout, so accepting larger values could
+;; silently under-align an object; reject them instead.
+(define (skip-c11-alignas! ps)
+ (expect-kw ps '_Alignas)
+ (expect-punct ps 'lparen)
+ (cond
+ ((%const-tok-is-decl? ps)
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n ty) (parse-declarator ps bty)))
+ (let ((al (ctype-align ty)))
+ (cond ((<= al 0)
+ (die (tok-loc (peek ps)) "_Alignas of incomplete type"))
+ ((> al 1)
+ (die (tok-loc (peek ps))
+ "_Alignas over-alignment unsupported" al))))))
+ (else
+ (let ((n (parse-const-int ps)))
+ (cond ((or (< n 0) (> n 1))
+ (die (tok-loc (peek ps))
+ "_Alignas over-alignment unsupported" n))))))
+ (expect-punct ps 'rparen))
+
(define (parse-decl-spec ps)
(let loop ((sto #f) (sn #f) (lg 0) (b #f) (saw #f))
(let ((t (peek ps)))
@@ -5277,8 +5381,11 @@
((at-kw? ps 'extern) (advance ps) (loop 'extern sn lg b #t))
((at-kw? ps 'typedef) (advance ps) (loop 'typedef sn lg b #t))
((or (at-kw? ps 'const) (at-kw? ps 'volatile)
- (at-kw? ps 'restrict) (at-kw? ps 'inline))
+ (at-kw? ps 'restrict) (at-kw? ps 'inline)
+ (at-kw? ps '_Noreturn))
(advance ps) (loop sto sn lg b #t))
+ ((at-kw? ps '_Alignas)
+ (skip-c11-alignas! ps) (loop sto sn lg b #t))
((at-kw? ps 'signed) (advance ps) (loop sto 'signed lg b #t))
((at-kw? ps 'unsigned) (advance ps) (loop sto 'unsigned lg b #t))
((at-kw? ps 'short) (advance ps) (loop sto sn -1 b #t))
@@ -5297,8 +5404,7 @@
((or (at-kw? ps '_Complex) (at-kw? ps '_Imaginary))
(advance ps) (loop sto sn lg b #t))
((or (at-kw? ps '_Atomic) (at-kw? ps '_Thread_local)
- (at-kw? ps '_Alignas) (at-kw? ps '_Generic)
- (at-kw? ps '_Alignof) (at-kw? ps '_Static_assert))
+ (at-kw? ps '_Generic))
(die (tok-loc t) "rejected" (tok-value t)))
((at-kw? ps 'struct)
(loop sto sn lg (parse-aggregate-spec ps 'struct) #t))
@@ -5395,6 +5501,8 @@
(let loop ((acc '()) (off 0))
(cond
((at-punct? ps 'rbrace) (reverse acc))
+ ((at-kw? ps '_Static_assert)
+ (parse-static-assert! ps) (loop acc off))
(else
(let-values (((_sto bty) (parse-decl-spec ps)))
(let dl ((acc2 acc) (o2 off))
@@ -5836,6 +5944,17 @@
(else
;; `sizeof EXPR` (no parens). Same no-eval rule.
(cons (%const-sizeof-expr ps #f) %t-word-u))))
+ (($ tok? (kind KW) (value _Alignof))
+ (advance ps)
+ (expect-punct ps 'lparen)
+ (cond
+ ((%const-tok-is-decl? ps)
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n ty) (parse-declarator ps bty)))
+ (expect-punct ps 'rparen)
+ (cons (max (ctype-align ty) 1) %t-word-u)))
+ (else
+ (cons (%const-alignof-expr ps) %t-word-u))))
(else (parse-const-primary ps)))))
;; Does TOK begin a type-name? Type specifiers, qualifiers,
@@ -5852,12 +5971,48 @@
(eq? v '_Complex) (eq? v '_Imaginary)
(eq? v 'struct) (eq? v 'union) (eq? v 'enum)
(eq? v 'const) (eq? v 'volatile) (eq? v 'restrict)
- (eq? v 'inline)))
+ (eq? v 'inline) (eq? v '_Noreturn)))
(($ tok? (kind IDENT) (value ,n)) (typedef? ps n))
(else #f)))
(define (%const-tok-is-decl? ps) (%tok-decl-start? ps (peek ps)))
+(define (%const-offsetof-field ps ty offset)
+ (let ((nt (advance ps)))
+ (cond ((not (eq? (tok-kind nt) 'IDENT))
+ (die (tok-loc nt) "__builtin_offsetof expects a member")))
+ (let ((f (%cg-find-field (%init-struct-fields ty) (tok-value nt))))
+ (cond ((not f)
+ (die (tok-loc nt) "__builtin_offsetof: no such member"
+ (tok-value nt))))
+ (cons (+ offset (car (cddr f))) (car (cdr f))))))
+
+(define (%const-builtin-offsetof ps)
+ ;; `__builtin_offsetof(T, member[const].nested)` is an integer constant.
+ ;; Reuse the aggregate field metadata and array element sizes already used by
+ ;; normal designators; no pointer expression or code generation is needed.
+ (expect-punct ps 'lparen)
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n ty) (parse-declarator ps bty)))
+ (expect-punct ps 'comma)
+ (let lp ((p (%const-offsetof-field ps ty 0)))
+ (pmatch (peek ps)
+ (($ tok? (kind PUNCT) (value dot))
+ (advance ps) (lp (%const-offsetof-field ps (cdr p) (car p))))
+ (($ tok? (kind PUNCT) (value lbrack))
+ (advance ps)
+ (let* ((index (parse-const-int ps))
+ (_ (expect-punct ps 'rbrack))
+ (aty (cdr p)))
+ (cond ((not (eq? (ctype-kind aty) 'arr))
+ (die (tok-loc (peek ps))
+ "__builtin_offsetof: subscript on non-array")))
+ (let ((elem (car (ctype-ext aty))))
+ (lp (cons (+ (car p) (* index (ctype-size elem))) elem)))))
+ (else
+ (expect-punct ps 'rparen)
+ (cons (car p) %t-word-u))))))
+
(define (parse-const-primary ps)
(let ((t (peek ps)))
(pmatch t
@@ -5873,10 +6028,15 @@
(let ((v (parse-const-expr ps)))
(expect-punct ps 'rparen) v))
(($ tok? (kind IDENT) (value ,n))
- (let ((sm (scope-lookup ps n)))
- (cond ((and sm (eq? (sym-kind sm) 'enum-const))
- (advance ps) (cons (sym-slot sm) %t-i32))
- (else (die (tok-loc t) "const-expr: not a constant" n)))))
+ (cond
+ ((bv= n "__builtin_offsetof")
+ (advance ps) (%const-builtin-offsetof ps))
+ (else
+ (let ((sm (scope-lookup ps n)))
+ (cond ((and sm (eq? (sym-kind sm) 'enum-const))
+ (advance ps) (cons (sym-slot sm) %t-i32))
+ (else
+ (die (tok-loc t) "const-expr: not a constant" n)))))))
(else (die (tok-loc t) "const-expr: bad operand"
(tok-value t))))))
@@ -5971,18 +6131,140 @@
;; not evaluated, so any emission or vstack push from the parse is
;; discarded. Returns the operand's byte size as a non-negative int.
;; If `paren?`, consumes the closing `)` after parsing.
+(define (%const-unevaluated-primary-type ps)
+ ;; File-scope sizeof/_Alignof has no active function buffer to snapshot.
+ ;; Cover the ordinary unevaluated-designator grammar directly so static
+ ;; initializers such as `sizeof table / sizeof table[0]` remain constant
+ ;; expressions without inventing a synthetic function.
+ (let ((t (peek ps)))
+ (pmatch t
+ (($ tok? (kind IDENT) (value ,n))
+ (let ((sm (scope-lookup ps n)))
+ (cond ((not sm) (die (tok-loc t) "unevaluated: undecl" n)))
+ (advance ps)
+ (sym-type sm)))
+ (($ tok? (kind INT) (value ,v))
+ (advance ps) (%c-int-type v))
+ (($ tok? (kind CHAR)) (advance ps) %t-i32)
+ (($ tok? (kind STR) (value ,v))
+ (advance ps) (%mk-arr %t-i8 (+ (bytevector-length v) 1)))
+ (($ tok? (kind PUNCT) (value lparen))
+ (cond
+ ((%const-paren-is-cast? ps)
+ ;; In an unevaluated operand the cast expression still has to be
+ ;; consumed, but only its result type matters. This covers the
+ ;; standard `sizeof(((T *)0)->field)` spelling without asking for an
+ ;; active function/codegen buffer at file scope.
+ (advance ps)
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n ty) (parse-declarator ps bty)))
+ (expect-punct ps 'rparen)
+ (%const-unevaluated-unary-type ps)
+ ty))
+ (else
+ (advance ps)
+ (let ((ty (%const-unevaluated-unary-type ps)))
+ (expect-punct ps 'rparen)
+ ty))))
+ (else (die (tok-loc t) "unevaluated: unsupported operand"
+ (tok-value t))))))
+
+(define (%const-unevaluated-postfix-type ps)
+ (let lp ((ty (%const-unevaluated-primary-type ps)))
+ (pmatch (peek ps)
+ (($ tok? (kind PUNCT) (value lbrack))
+ (advance ps)
+ ;; The index is itself unevaluated by sizeof, but parsing it as a
+ ;; constant keeps token consumption deterministic for the bootstrap
+ ;; subset (array tables conventionally use [0]).
+ (parse-const-int ps)
+ (expect-punct ps 'rbrack)
+ (let ((k (ctype-kind ty)))
+ (cond ((eq? k 'arr) (lp (car (ctype-ext ty))))
+ ((eq? k 'ptr) (lp (ctype-ext ty)))
+ (else (die #f "unevaluated: subscript on non-array" k)))))
+ (($ tok? (kind PUNCT) (value dot))
+ (advance ps)
+ (let ((nt (peek ps)))
+ (cond ((not (eq? (tok-kind nt) 'IDENT))
+ (die (tok-loc nt) "unevaluated: field")))
+ (advance ps)
+ (let ((f (%cg-find-field (car (cddr (ctype-ext ty)))
+ (tok-value nt))))
+ (cond ((not f) (die (tok-loc nt) "unevaluated: no field"
+ (tok-value nt))))
+ (lp (cadr f)))))
+ (($ tok? (kind PUNCT) (value arrow))
+ (advance ps)
+ (cond ((not (eq? (ctype-kind ty) 'ptr))
+ (die #f "unevaluated: arrow on non-pointer")))
+ (let* ((sty (ctype-ext ty)) (nt (peek ps)))
+ (cond ((not (eq? (tok-kind nt) 'IDENT))
+ (die (tok-loc nt) "unevaluated: field")))
+ (advance ps)
+ (let ((f (%cg-find-field (car (cddr (ctype-ext sty)))
+ (tok-value nt))))
+ (cond ((not f) (die (tok-loc nt) "unevaluated: no field"
+ (tok-value nt))))
+ (lp (cadr f)))))
+ (else ty))))
+
+(define (%const-unevaluated-unary-type ps)
+ (pmatch (peek ps)
+ (($ tok? (kind PUNCT) (value amp))
+ (advance ps) (%mk-ptr (%const-unevaluated-unary-type ps)))
+ (($ tok? (kind PUNCT) (value star))
+ (advance ps)
+ (let ((ty (%const-unevaluated-unary-type ps)))
+ (cond ((not (eq? (ctype-kind ty) 'ptr))
+ (die #f "unevaluated: dereference of non-pointer")))
+ (ctype-ext ty)))
+ (($ tok? (kind PUNCT) (value plus))
+ (advance ps)
+ (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
+ (($ tok? (kind PUNCT) (value minus))
+ (advance ps)
+ (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
+ (($ tok? (kind PUNCT) (value tilde))
+ (advance ps)
+ (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
+ (($ tok? (kind PUNCT) (value bang))
+ (advance ps) (%const-unevaluated-unary-type ps) %t-i32)
+ (else (%const-unevaluated-postfix-type ps))))
+
(define (%const-sizeof-expr ps paren?)
(cond
((not (ps-cg ps))
(die #f "#if: sizeof of expression not valid in preprocessor context"))
- (else
+ ((cg-in-fn? (ps-cg ps))
(let ((tag (cg-snapshot (ps-cg ps))))
(cond (paren? (parse-expr ps) (expect-punct ps 'rparen))
(else (parse-unary ps)))
(let* ((tp (cg-top (ps-cg ps)))
(sz (max (ctype-size (opnd-type tp)) 0)))
(cg-rewind (ps-cg ps) tag)
- sz)))))
+ sz)))
+ (else
+ (let ((ty (%const-unevaluated-unary-type ps)))
+ (cond (paren? (expect-punct ps 'rparen)))
+ (max (ctype-size ty) 0)))))
+
+(define (%const-alignof-expr ps)
+ (cond
+ ((not (ps-cg ps))
+ (die #f "#if: _Alignof of expression not valid in preprocessor context"))
+ ((not (cg-in-fn? (ps-cg ps)))
+ (let ((ty (%const-unevaluated-unary-type ps)))
+ (expect-punct ps 'rparen)
+ (max (ctype-align ty) 1)))
+ (else
+ (let ((tag (cg-snapshot (ps-cg ps))))
+ (parse-expr ps)
+ (expect-punct ps 'rparen)
+ (let* ((tp (cg-top (ps-cg ps)))
+ (al (max (ctype-align (opnd-type tp)) 1)))
+ (cg-rewind (ps-cg ps) tag)
+ al)))))
;; Convenience: returns the integer value alone (callers that don't
;; need the type half of parse-const-expr's (value . ctype) result).
@@ -6111,11 +6393,28 @@
(let ((loc (tok-loc (peek ps))))
(debug-log "decl" "line" (loc-line loc)
"heap" (heap-usage)))))
- (parse-decl-or-fn ps)
+ (cond ((at-kw? ps '_Static_assert) (parse-static-assert! ps))
+ (else (parse-decl-or-fn ps)))
;; Function-local metadata is not part of the persistent world.
(cg-fn-meta-set! (ps-cg ps) '())
(loop)))))
+(define (parse-static-assert! ps)
+ (let ((loc (tok-loc (peek ps))))
+ (expect-kw ps '_Static_assert)
+ (expect-punct ps 'lparen)
+ (let ((v (parse-const-int ps)))
+ (expect-punct ps 'comma)
+ (cond ((not (eq? (tok-kind (peek ps)) 'STR))
+ (die (tok-loc (peek ps)) "_Static_assert needs a string")))
+ (let strings ()
+ (cond ((eq? (tok-kind (peek ps)) 'STR)
+ (advance ps) (strings))))
+ (expect-punct ps 'rparen)
+ (expect-punct ps 'semi)
+ (cond ((%c-value-zero? v) (die loc "static assertion failed"))
+ (else #t)))))
+
(define (parse-decl-or-fn ps)
(let-values (((sto b) (parse-decl-spec ps)))
(cond
@@ -6228,15 +6527,25 @@
((ctype-is-fn? ty)
(scope-bind! ps n
(%sym n 'fn (or sto 'extern) ty #f #f)))
- ;; §I: block-scope `static` routes to a global with a name mangled
- ;; on the enclosing function so two functions can each have their
- ;; own `static int n;` without colliding. The sym's NAME holds the
- ;; mangled form (cg-push-sym / cg-emit-global both prefix "cc__"
- ;; onto sym-name to derive the emitted label); scope-bind!s key
- ;; remains the original identifier for source-level lookup.
+ ;; §I: block-scope `static` routes to a global with a declaration-id
+ ;; name under the enclosing function. Function + identifier is not
+ ;; enough: separate nested scopes may legally reuse the same identifier.
+ ;; A same-scope redeclaration reuses its existing mangled name; a new
+ ;; declaration consumes the function's monotonic static counter. The
+ ;; scope-bind! key remains the original identifier for source lookup.
((and (eq? sto 'static) (ps-fn-ctx ps))
- (let* ((fname (fn-ctx-name (ps-fn-ctx ps)))
- (mangled (bytevector-append fname "__" n)))
+ (let* ((fc (ps-fn-ctx ps))
+ (old (scope-lookup-current ps n))
+ (mangled
+ (cond
+ ((and old (eq? (sym-kind old) 'var)
+ (eq? (sym-storage old) 'static))
+ (sym-name old))
+ (else
+ (let ((id (fn-ctx-static-counter fc)))
+ (fn-ctx-static-counter-set! fc (+ id 1))
+ (bytevector-append (fn-ctx-name fc) "__static_"
+ (number->string id 10) "__" n))))))
(cond
((at-punct? ps 'assign)
(advance ps)
@@ -6383,6 +6692,16 @@
;; - (T){...} (file-scope compound literal) -> (label-ref . cc__cl_N)
(let ((t (peek ps)))
(cond
+ ;; Redundant expression grouping around a static initializer. Macro
+ ;; APIs commonly spell literals as `((T){...})` and strings as
+ ;; `("...")`; peel one non-cast pair and let the regular initializer
+ ;; paths consume the enclosed value.
+ ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'lparen)
+ (eq? (tok-kind (peek2 ps)) 'STR))
+ (advance ps)
+ (let ((p (%const-init-piece ps ty)))
+ (expect-punct ps 'rparen)
+ p))
;; Address initializer: &ident -> label-ref
((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'amp))
(advance ps)
@@ -6397,7 +6716,7 @@
(and (eq? (sym-kind sm) 'var)
(or (eq? (sym-storage sm) 'static)
(eq? (sym-storage sm) 'extern))))
- (cons 'label-ref (%cg-sym-label sm)))
+ (cons 'label-ref (%cg-sym-label (ps-cg ps) sm)))
(else
(die (tok-loc it) "init: &x must reference a global"
(tok-value it))))))
@@ -6466,7 +6785,7 @@
(eq? (sym-storage sm) 'extern)))))))
(advance ps)
(let ((sm (scope-lookup ps (tok-value t))))
- (cons 'label-ref (%cg-sym-label sm))))
+ (cons 'label-ref (%cg-sym-label (ps-cg ps) sm))))
;; Plain string literal as char* initializer.
((eq? (tok-kind t) 'STR)
(advance ps)
@@ -6477,6 +6796,52 @@
(let ((v (parse-const-int ps)))
(%int->le-bv v (max (ctype-size ty) 1)))))))
+;; Parse a file-scope aggregate compound literal, accepting redundant outer
+;; grouping: `(T){...}` and `((T){...})`. The caller already knows an
+;; aggregate is required, so a leading parenthesis is unambiguous here.
+(define (%global-init-compound ps expected-ty)
+ (expect-punct ps 'lparen)
+ (cond
+ ((at-punct? ps 'lparen)
+ (let ((pieces (%global-init-compound ps expected-ty)))
+ (expect-punct ps 'rparen)
+ pieces))
+ (else
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n literal-ty) (parse-declarator ps bty)))
+ (expect-punct ps 'rparen)
+ (cond ((not (or (ctype-compat? literal-ty expected-ty)
+ (%ctype-same-aggregate-tag? literal-ty expected-ty)))
+ (die (tok-loc (peek ps))
+ "init: incompatible aggregate compound literal")))
+ (expect-punct ps 'lbrace)
+ (let ((k (ctype-kind literal-ty)))
+ (cond
+ ((eq? k 'arr)
+ (let-values (((pieces _count)
+ (%parse-init-array-list ps literal-ty)))
+ pieces))
+ ((or (eq? k 'struct) (eq? k 'union))
+ (%parse-init-struct-list ps literal-ty))
+ (else (die #f "init: compound literal is not aggregate" k))))))))
+
+(define (%ctype-same-aggregate-tag? a b)
+ ;; Qualifier propagation can clone a tagged aggregate ctype, so pointer
+ ;; identity alone is too strict for `(T){...}` assigned to a const-qualified
+ ;; T field. A matching non-anonymous tag is the seed compiler's nominal
+ ;; identity for structs/unions.
+ (let ((ka (ctype-kind a)) (kb (ctype-kind b)))
+ (and (eq? ka kb)
+ (or (eq? ka 'struct) (eq? ka 'union))
+ (let ((ta (car (ctype-ext a))) (tb (car (ctype-ext b))))
+ (and ta tb (bytes=? ta tb))))))
+
+(define (%init-grouped-compound-start? ps)
+ (or (%const-paren-is-cast? ps)
+ (pmatch (peek2 ps)
+ (($ tok? (kind PUNCT) (value lparen)) #t)
+ (else #f))))
+
(define (%init-array-elem-type ty)
(cond ((eq? (ctype-kind ty) 'arr) (car (ctype-ext ty)))
(else (die #f "init: not an array" ty))))
@@ -6507,6 +6872,30 @@
((bytes=? (car (car fields)) fname) (cdr fields))
(else (%init-drop-thru-field (cdr fields) fname))))
+;; Consume a C99 field designator, including a chain such as `.v.stack`.
+;; Return the top-level field name (for positional-cursor advancement), the
+;; final selected type, and its aggregate-relative byte offset.
+(define (%init-field-designator ps fields)
+ (expect-punct ps 'dot)
+ (let walk ((cur-fields fields) (top-name #f) (offset 0))
+ (let ((nt (advance ps)))
+ (cond ((not (eq? (tok-kind nt) 'IDENT))
+ (die (tok-loc nt) "init: .field expects ident")))
+ (let ((f (%cg-find-field cur-fields (tok-value nt))))
+ (cond ((not f) (die (tok-loc nt) "init: no such field"
+ (tok-value nt))))
+ (let ((name (car f))
+ (fty (car (cdr f)))
+ (foff (car (cddr f))))
+ (cond
+ ((at-punct? ps 'dot)
+ (advance ps)
+ (walk (%init-struct-fields fty)
+ (or top-name name) (+ offset foff)))
+ (else
+ (expect-punct ps 'assign)
+ (list (or top-name name) fty (+ offset foff)))))))))
+
;; #t when TY is an array of i8/u8 — a char[] a string literal may
;; initialize directly (C11 §6.7.9 ¶14).
(define (%char-arr-type? t)
@@ -6541,6 +6930,17 @@
(define (%global-init-elem ps t elide?)
(let ((k (ctype-kind t)))
(cond
+ ((and elide?
+ (or (eq? k 'arr) (eq? k 'struct) (eq? k 'union))
+ (at-punct? ps 'lparen)
+ (%init-grouped-compound-start? ps))
+ (let ((pieces (%global-init-compound ps t)))
+ (cond
+ (elide? pieces)
+ (else
+ (cond ((at-punct? ps 'comma) (advance ps)))
+ (expect-punct ps 'rbrace)
+ pieces))))
;; char[] initialized by a string literal (`{"..."}` or, under
;; brace elision, a bare `"..."`). Without this, the `arr` arm
;; below would treat the string as an element-list and encode it
@@ -6577,6 +6977,67 @@
;; %global-init-elem but emits per-element store ops via cg-assign for
;; scalar leaves, and recurses into the local-list walkers for
;; aggregates. Returns 0; the side effect is the emitted code.
+(define (%local-init-expr-tokens ps)
+ ;; Capture one assignment-expression without consuming the comma or `}`
+ ;; which terminates it in the surrounding initializer list. Braces must
+ ;; be tracked separately from parentheses: a compound literal such as
+ ;; `(struct S){ .x = 1, .y = 2 }` is one expression even though it contains
+ ;; both commas and a closing brace.
+ (let loop ((acc '()) (paren 0) (brack 0) (brace 0))
+ (let ((t (peek ps)))
+ (cond
+ ((eq? (tok-kind t) 'EOF)
+ (die (tok-loc t) "EOF in local initializer expression"))
+ ((and (= paren 0) (= brack 0) (= brace 0)
+ (eq? (tok-kind t) 'PUNCT)
+ (or (eq? (tok-value t) 'comma)
+ (eq? (tok-value t) 'rbrace)))
+ (reverse acc))
+ (else
+ (let ((nt (advance ps)))
+ (cond
+ ((not (eq? (tok-kind nt) 'PUNCT))
+ (loop (cons nt acc) paren brack brace))
+ ((eq? (tok-value nt) 'lparen)
+ (loop (cons nt acc) (+ paren 1) brack brace))
+ ((eq? (tok-value nt) 'rparen)
+ (loop (cons nt acc) (- paren 1) brack brace))
+ ((eq? (tok-value nt) 'lbrack)
+ (loop (cons nt acc) paren (+ brack 1) brace))
+ ((eq? (tok-value nt) 'rbrack)
+ (loop (cons nt acc) paren (- brack 1) brace))
+ ((eq? (tok-value nt) 'lbrace)
+ (loop (cons nt acc) paren brack (+ brace 1)))
+ ((eq? (tok-value nt) 'rbrace)
+ (loop (cons nt acc) paren brack (- brace 1)))
+ (else
+ (loop (cons nt acc) paren brack brace)))))))))
+
+(define (%local-init-aggregate-expr? ps expected)
+ ;; A brace-less aggregate initializer is ambiguous until its first
+ ;; assignment-expression has been typed: it can be a whole-aggregate copy
+ ;; (`.member = value`) or brace elision (`.member = 1, 2`). Parse that
+ ;; expression once under a codegen snapshot, then put its tokens back. The
+ ;; real parse below either copies the aggregate or descends into its first
+ ;; scalar member. This is the same no-side-effect mechanism used by sizeof.
+ (let* ((it (ps-iter ps))
+ (toks (%local-init-expr-tokens ps)))
+ (for-each (lambda (t) (iter-unget! it t)) (reverse toks))
+ (cond
+ ((null? toks) #f)
+ (else
+ (let ((tag (cg-snapshot (ps-cg ps))))
+ (parse-saved-expr ps toks)
+ (let* ((top (cg-top (ps-cg ps)))
+ (actual (opnd-type top))
+ (aggregate?
+ (and (or (eq? (ctype-kind actual) 'struct)
+ (eq? (ctype-kind actual) 'union))
+ (or (ctype-compat? actual expected)
+ (%ctype-same-aggregate-tag? actual expected)))))
+ (cg-rewind (ps-cg ps) tag)
+ aggregate?))))))
+
(define (%local-init-elem ps sm eoff t elide?)
(let ((k (ctype-kind t)))
(cond
@@ -6611,6 +7072,10 @@
(else (%parse-init-local-array-list ps sm eoff t))))
((or (eq? k 'struct) (eq? k 'union))
(cond
+ ((and elide? (%local-init-aggregate-expr? ps t))
+ (%push-frame-elem-lval ps eoff t)
+ (parse-expr-bp ps 4)
+ (cg-copy-struct (ps-cg ps)))
(elide? (%parse-init-local-struct-list/mode ps sm eoff t #f))
(else (%parse-init-local-struct-list ps sm eoff t))))
(else
@@ -6702,24 +7167,35 @@
(let* ((elem (%init-array-elem-type ty))
(esize (ctype-size elem))
(decl (%init-array-decl-len ty)))
- (let lp ((acc '()) (count 0))
+ (let lp ((entries '()) (cursor 0) (count 0))
(cond
((cond (brace? (at-punct? ps 'rbrace))
- (else (or (>= count (cond ((< decl 0) 0) (else decl)))
+ (else (or (>= cursor (cond ((< decl 0) 0) (else decl)))
(at-punct? ps 'rbrace)
(at-punct? ps 'dot)
(at-punct? ps 'lbrack))))
(cond (brace? (advance ps)))
- ;; Pad to declared length if longer than count.
- (let* ((final (cond ((< decl 0) count) (else decl)))
- (pad (- final count)))
- (values
- (cond
- ((> pad 0) (reverse (cons (%pad-piece (* pad esize)) acc)))
- (else (reverse acc)))
- count)))
+ (let ((final (cond ((< decl 0) count) (else decl))))
+ (values (%merge-init-entries (reverse entries) (* final esize))
+ count)))
(else
- (let ((piece
+ (let* ((designated? (and brace? (at-punct? ps 'lbrack)))
+ (index
+ (cond
+ (designated?
+ (advance ps)
+ (let ((n (parse-const-int ps)))
+ (expect-punct ps 'rbrack)
+ (expect-punct ps 'assign)
+ n))
+ (else cursor)))
+ (_range
+ (cond
+ ((or (< index 0) (and (>= decl 0) (>= index decl)))
+ (die (tok-loc (peek ps))
+ "init: array designator out of range" index))
+ (else 0)))
+ (piece
(let ((p
(cond
((at-punct? ps 'lbrace)
@@ -6737,12 +7213,14 @@
;; no-brace: consume comma only if more items
;; remain in our quota.
(cond
- ((and (< (+ count 1)
+ ((and (< (+ index 1)
(cond ((< decl 0) 0) (else decl)))
(at-punct? ps 'comma))
(advance ps)))))
- p)))
- (lp (%init-prepend-reversed piece acc) (+ count 1))))))))
+ p))
+ (next (+ index 1)))
+ (lp (cons (cons (* index esize) piece) entries)
+ next (max count next))))))))
(define (%piece-bytesize p)
;; Output width of one piece (cf. %cg-init-piece->bv): a bv emits
@@ -6833,19 +7311,9 @@
(else
(let* ((designated? (at-punct? ps 'dot))
(target
- (cond
- (designated?
- (advance ps)
- (let ((nt (advance ps)))
- (cond
- ((not (eq? (tok-kind nt) 'IDENT))
- (die (tok-loc nt) "init: .field expects ident")))
- (let ((f (%cg-find-field fields (tok-value nt))))
- (cond
- ((not f) (die (tok-loc nt) "init: no such field"
- (tok-value nt))))
- (expect-punct ps 'assign)
- f)))
+ (cond
+ (designated?
+ (%init-field-designator ps fields))
((null? rest)
(die (tok-loc (peek ps)) "init: too many fields"))
(else (car rest))))
@@ -7063,16 +7531,9 @@
(else
(let* ((designated? (at-punct? ps 'dot))
(target
- (cond
- (designated?
- (advance ps)
- (let ((nt (advance ps)))
- (let ((f (%cg-find-field fields (tok-value nt))))
- (cond
- ((not f) (die (tok-loc nt) "init: no such field"
- (tok-value nt))))
- (expect-punct ps 'assign)
- f)))
+ (cond
+ (designated?
+ (%init-field-designator ps fields))
((null? rest)
(die (tok-loc (peek ps)) "init: too many fields"))
(else (car rest))))
@@ -7114,7 +7575,7 @@
(par (cadr e)) (var (car (cddr e))))
(let ((psyms (cg-fn-begin/v (ps-cg ps) name par ret var)))
(ps-fn-ctx-set! ps
- (%fn-ctx name ret (map cdr psyms) var '()))
+ (%fn-ctx name ret (map cdr psyms) var '() 0))
(scope-enter! ps)
(for-each (lambda (p) (scope-bind! ps (car p) (cdr p)))
psyms)
@@ -7141,6 +7602,7 @@
(advance ps) (expect-punct ps 'semi) (do-continue ps))
(($ tok? (kind KW) (value case)) (parse-case-stmt ps))
(($ tok? (kind KW) (value default)) (parse-default-stmt ps))
+ (($ tok? (kind KW) (value _Static_assert)) (parse-static-assert! ps))
(($ tok? (kind IDENT))
(guard (and (eq? (tok-kind (peek2 ps)) 'PUNCT)
(eq? (tok-value (peek2 ps)) 'colon)))
@@ -7607,6 +8069,18 @@
(expect-punct ps 'rparen)
(cg-push-imm (ps-cg ps) %t-word-u
(max (ctype-size ty) 0))))
+ ;; A string literal has type char[N] until ordinary expression
+ ;; conversion. parse-primary deliberately lowers strings as
+ ;; pointers for runtime expressions, so preserve the array bound
+ ;; here before entering that path. Kit's KIT_SLICE_LIT relies on
+ ;; exactly this `sizeof("...") - 1` shape during parser setup.
+ ((and (eq? (tok-kind (peek ps)) 'STR)
+ (eq? (tok-kind (peek2 ps)) 'PUNCT)
+ (eq? (tok-value (peek2 ps)) 'rparen))
+ (let ((n (+ (bytevector-length (tok-value (peek ps))) 1)))
+ (advance ps)
+ (expect-punct ps 'rparen)
+ (cg-push-imm (ps-cg ps) %t-word-u n)))
(else
;; sizeof(EXPR): C semantics — operand is NOT evaluated.
;; Snapshot cg state, parse the expr to learn its type,
@@ -7621,12 +8095,36 @@
(cg-push-imm (ps-cg ps) %t-word-u sz))))))
(else
;; sizeof EXPR (no parens) — same no-eval rule.
+ (cond
+ ((eq? (tok-kind (peek ps)) 'STR)
+ (let ((n (+ (bytevector-length (tok-value (peek ps))) 1)))
+ (advance ps)
+ (cg-push-imm (ps-cg ps) %t-word-u n)))
+ (else
+ (let ((tag (cg-snapshot (ps-cg ps))))
+ (parse-unary ps)
+ (let* ((tp (cg-top (ps-cg ps)))
+ (sz (max (ctype-size (opnd-type tp)) 0)))
+ (cg-rewind (ps-cg ps) tag)
+ (cg-push-imm (ps-cg ps) %t-word-u sz))))))))
+ (($ tok? (kind KW) (value _Alignof))
+ (advance ps)
+ (expect-punct ps 'lparen)
+ (cond
+ ((token-is-decl? ps)
+ (let*-values (((_sto bty) (parse-decl-spec ps))
+ ((_n ty) (parse-declarator ps bty)))
+ (expect-punct ps 'rparen)
+ (cg-push-imm (ps-cg ps) %t-word-u
+ (max (ctype-align ty) 1))))
+ (else
(let ((tag (cg-snapshot (ps-cg ps))))
- (parse-unary ps)
+ (parse-expr ps)
+ (expect-punct ps 'rparen)
(let* ((tp (cg-top (ps-cg ps)))
- (sz (max (ctype-size (opnd-type tp)) 0)))
+ (al (max (ctype-align (opnd-type tp)) 1)))
(cg-rewind (ps-cg ps) tag)
- (cg-push-imm (ps-cg ps) %t-word-u sz))))))
+ (cg-push-imm (ps-cg ps) %t-word-u al))))))
(else (parse-postfix ps))))
(define (token-is-decl? ps) (%tok-decl-start? ps (peek ps)))
@@ -7880,6 +8378,13 @@
(cg-va-end (ps-cg ps))
(cg-push-imm (ps-cg ps) %t-i32 0))
+(define (parse-builtin-offsetof ps)
+ ;; Reuse the constant aggregate-designator parser in ordinary expression
+ ;; context; offsetof emits no code beyond materializing its integer value.
+ (advance ps) ; IDENT
+ (let ((v (%const-builtin-offsetof ps)))
+ (cg-push-imm (ps-cg ps) (cdr v) (car v))))
+
(define (parse-primary ps)
(let ((t (peek ps)))
(pmatch t
@@ -7899,6 +8404,7 @@
((bv= n "__builtin_va_arg") (parse-builtin-va-arg ps))
((bv= n "__builtin_va_end") (parse-builtin-va-end ps))
((bv= n "__builtin_expect") (parse-builtin-expect ps))
+ ((bv= n "__builtin_offsetof") (parse-builtin-offsetof ps))
(else
(let ((sm (scope-lookup ps n)))
(advance ps)
@@ -7977,9 +8483,9 @@
;; --lib=PFX selects library-mode codegen: cc.scm skips the p1_main
;; entry stub and trailing :ELF_end (the catm chain supplies them
-;; from P1/entry-*.P1pp + P1/elf-end.P1pp once), 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. Returns
+;; from P1/entry-*.P1pp + P1/elf-end.P1pp once), and uses PFX as the
+;; translation-unit namespace for internal-linkage symbols, strings,
+;; compound literals, frame macros, and generated labels. Returns
;; (values prefix-bv rest-args). PREFIX = "" means exec mode (flag
;; absent). PREFIX = "" with the flag present is rejected — silently
;; falling back to exec mode would mask a typo'd Makefile rule.
diff --git a/docs/CC.md b/docs/CC.md
@@ -262,11 +262,27 @@ char *(*tab[5])(int) // array of 5 pointers to function (int) returning char*
- `static` at file scope — gives internal linkage; prevents the symbol
from being emitted as a P1pp `:public_label`. Honored.
- `static` at block scope — single shared instance, zero-initialized
- by default. Honored.
+ by default. Honored. Distinct declarations in nested lexical scopes receive
+ distinct emitted identities even when they reuse the same C identifier.
- `auto` — accepted, no effect (the default for block scope).
- `register` — accepted, no effect.
- `typedef` — handled specially (see Types).
+### C11 declaration forms used by Kit0
+
+- `_Noreturn` is accepted as a declaration specifier and has no P1 codegen
+ effect.
+- `_Static_assert(integer-constant-expression, "message");` is accepted at
+ file, block, and aggregate-member scope. A false expression is a compile
+ error.
+- `_Alignof(type)` and `_Alignof expression` use the existing target type
+ metadata. The GNU spellings `__alignof` and `__alignof__` are aliases.
+- `__builtin_offsetof(type, member.designator)` is an integer constant and
+ supports nested member and constant array-subscript steps.
+- `_Alignas(type)` and `_Alignas(constant)` accept only zero or one/`char`
+ alignment. Those requests cannot change any object's natural layout;
+ over-aligned storage is rejected because the seed layout cannot represent it.
+
### Function definitions
```
@@ -286,17 +302,25 @@ K&R-style (`int f(a, b) int a, b; { … }`) is **not** supported.
- Scalars: `T x = expr;` — `expr` must be a constant for static-storage
variables; arbitrary for auto-storage.
- Arrays: `T a[N] = { e0, e1, ... };` and `T a[] = { ... };` (size
- inferred). String-literal initializer for `char[]` allowed.
+ inferred). String-literal initializer for `char[]` and constant array
+ designators such as `{ [2] = value }` are supported.
- Structs: `S s = { e0, e1, ... };` (positional). Designated
- initializers (`{ .field = ... }`) **supported** at struct top level
- only — required by tcc.c.
+ initializers (`{ .field = ... }`) support nested field chains such as
+ `.outer.inner = value`.
- Nested initializers brace-flatten the obvious way.
+- For local nested aggregate members, a compatible aggregate expression is a
+ whole-member assignment; other initializer tokens continue through brace
+ elision. This includes designated aggregate compound literals.
+- Aggregate compound literals are accepted in aggregate
+ initializers, including redundant grouping from macros such as
+ `#define S(x) ((Slice){ .text = x, .len = sizeof(x)-1 })`.
### Inline / attributes
- `inline` — already removed by `-D inline=` in the bootstrap. Our
preprocessor would also strip the keyword if it appeared. No
effect on codegen either way.
+- `_Noreturn` — parsed and discarded; P1 has no return-analysis metadata.
- `__attribute__((...))` — parsed and discarded everywhere it
appears in declarations.
@@ -321,7 +345,7 @@ Cut:
- statement expressions `({ ... })` (GCC ext) — tcc.c doesn't use them
- `__label__` (GCC) — N/A
-- compound literals `(T){ ... }` — tcc.c doesn't use them
+- compound literals outside the aggregate-initializer subset
- `_Generic` selection — tcc.c doesn't use it
- inline asm `__asm__(...)` — N/A; tcc.c gates this on conditions
that aren't active at the tcc-mes stage
@@ -348,17 +372,20 @@ All standard C operators with standard precedence and associativity:
Notes:
- `sizeof T` and `sizeof e` both supported. `sizeof e` does **not**
- evaluate `e` (standard).
+ evaluate `e` (standard). Its speculative codegen snapshot restores value
+ stack, emission offset, frame allocation and indirect-slot metadata,
+ outgoing-call size, and label state. An immediate string-literal operand
+ retains its `char[N]` type for this operation, so `sizeof("abc") == 4`.
- Integer promotion (rank ≤ `int` → `int`) and usual arithmetic
conversions performed automatically. Pointer arithmetic scales by
pointee size.
- Implicit conversions for assignment, return, and function arguments
(incl. promotion of variadic args to `int` / `unsigned int` /
pointer / `long` / `unsigned long`).
-- String literals have type `char *` (not `const char[N]`) for our
- purposes — we strip const, and tcc.c writes through string literals
- in a few places.
-- `_Alignof` — **not** supported. tcc.c uses no alignment intrinsics.
+- Outside `sizeof`, string literals use the bootstrap-level `char *` model —
+ we strip const, and tcc.c writes through string literals in a few places.
+- `_Alignof` and its GNU aliases work for types and unevaluated designator
+ expressions, including file-scope array/member/subscript expressions.
### Variadic argument access
@@ -381,10 +408,12 @@ The compiler ABI has four target-word argument registers (`a0`–`a3`) and
word-indexed stack arguments after them. A scalar or pointer consumes one
slot; an RV32 `long long` consumes two consecutive slots and is returned in
`a0:a1`. Aggregates of at most two target words use the same direct-register
-convention; wider aggregate returns pass a hidden result pointer in `a0`.
-Variadic functions reserve a contiguous 16-target-word save area, and
+convention; wider aggregate arguments are copied by the caller and passed by
+an invisible pointer, preserving C by-value semantics. Wider aggregate returns
+pass a hidden result pointer in `a0`.
+Variadic functions reserve a contiguous 32-target-word save area, and
`va_arg(ap, long long)` advances by two RV32 words. Calls exceeding that
-documented 16-word cap are rejected rather than silently miscompiled.
+documented 32-word cap are rejected rather than silently miscompiled.
## Standard library expectations
@@ -424,12 +453,12 @@ Kept explicit so additions are deliberate.
| Bitfields | rejected | HAVE_BITFIELD off |
| `setjmp` / `longjmp` | not lib | HAVE_SETJMP off |
| VLAs | rejected | tcc.c doesn't use; complicates frame layout |
-| Compound literals `(T){...}` | rejected | tcc.c doesn't use |
+| Compound literals `(T){...}` | limited | aggregate-initializer subset for Kit0 |
| Statement expressions `({...})` (GCC) | rejected | tcc.c doesn't use |
| `_Generic` | rejected | not used |
| `_Atomic`, `_Thread_local` | rejected | not used |
-| `_Alignof`, `_Alignas` | rejected | not used |
-| `_Static_assert` | rejected | not used |
+| `_Alignof`, `_Alignas` | supported/limited | `_Alignas` accepts only layout-neutral 0/1 |
+| `_Static_assert`, `_Noreturn` | supported | assertion checked; noreturn metadata ignored |
| Wide / UTF strings (`L"…"`, `u8"…"`) | rejected | not used |
| Anonymous struct/union members | rejected | not used |
| Multi-character constants (`'AB'`) | rejected | not used |
diff --git a/docs/HEX2pp.md b/docs/HEX2pp.md
@@ -200,3 +200,11 @@ lookup for a non-dotted name ignores scope.
Both labels in `LABEL-OTHER` have known addresses by the start of pass 2, so
the subtraction is a single operation at emit time. No third pass is
required.
+
+## Limits
+
+The seed implementation accepts up to 128 MiB of input and emits up to
+128 MiB. Its fixed arenas for input, output, token text, labels, fixups, and
+scope history end at about 424 MiB past `ELF_end`, within the 512 MiB writable
+mapping supplied by the seed ELF headers. The C reference enforces the same
+input and output limits.
diff --git a/docs/KIT0.md b/docs/KIT0.md
@@ -0,0 +1,128 @@
+# Kit0 integration
+
+boot2 can compile a prepared Kit bootstrap profile as separate translation
+units and link the resulting P1pp through its ordinary per-architecture
+P1/M1pp/hex2pp path. The supported boot2 architecture names are `aarch64`,
+`amd64`, and `riscv64`; RV32 has no corresponding Kit0 profile. Kit currently
+supplies the `aa64` preparation; the `x64` and `rv64` mappings are ready for
+the corresponding profiles when they are added.
+
+Prepare and check from the Kit worktree:
+
+```sh
+cd ../../kit
+gmake bootstrap-aa64-prepare
+KIT0_SWEEP_JOBS=2 gmake bootstrap-aa64-check
+```
+
+Or run only the boot2 link from this worktree:
+
+```sh
+gmake ARCH=aarch64 DRIVER=podman \
+ KIT0_PREP="$(cd ../../kit && pwd)/build/bootstrap-aa64/kit0-prep" kit0
+podman run --rm --pull=never --platform linux/arm64 \
+ -v "$(pwd):/boot2" -w /boot2 boot2-busybox-test:aarch64 \
+ /boot2/build/aarch64/podman/kit0/kit0 --version
+```
+
+When `KIT0_PREP` is omitted, the Makefile maps boot2 architecture names to the
+conventional Kit profile suffixes: `aarch64` to `aa64`, `amd64` to `x64`, and
+`riscv64` to `rv64`. `KIT_DIR` defaults to `../../kit`, `KIT0_PROFILE` can
+override just the profile suffix, and an explicit `KIT0_PREP` can point
+anywhere. The selected preparation must enable the matching
+`KIT_ARCH_*_ENABLED` setting in `resolved-config.h`; this prevents accidentally
+framing, assembling, or running one target's prepared sources as another
+target.
+
+`boot/kit0.sh` validates the prepared `manifest.tsv`, `resolved-config.h`, and
+`m1pp-partitions.tsv`, then imports the preprocessed `tu/*.i` files and
+`kit0-run.scm`. The partition manifest is the inspectable record of the order
+encoded in that recipe. The recipe compiles mes-libc and each Kit TU
+separately. It expands the M1pp runtime
+once, then independently expands entry, libc, every Kit TU, and the ELF
+terminator by replaying the common macro prelude and using `%reset-output`
+with disjoint hygiene-label ranges. Each non-runtime partition begins with
+`.align 16`. The ordered hex2pp partitions are concatenated, prefixed with
+the ELF framing, and assembled by the existing hex2pp stage. It does not copy
+generated Kit inputs into this worktree.
+
+## General cc.scm additions
+
+Kit0 exposed a few ordinary C gaps. They are implemented without Kit source
+or filename special cases:
+
+- `_Noreturn`, checked `_Static_assert`, `_Alignof` (plus GNU aliases), and
+ layout-neutral `_Alignas`; motivated by Kit's public/config/core headers.
+- File-scope unevaluated `sizeof`/alignment designators, array/nested-field
+ designators, and aggregate compound literals in global initializers;
+ motivated by Kit's source tables and `KIT_SLICE_LIT`-style initializers.
+- `__builtin_offsetof` over member/constant-subscript designators in both
+ integer-constant and ordinary expression contexts, motivated by Kit's
+ aggregate-layout assertions and container recovery in `link_script.c`.
+- By-value calls for aggregates wider than two target words, implemented as a
+ caller-owned copy passed through an invisible pointer; motivated by Kit's
+ context/option structures. Direct aggregate arguments are addressed and
+ copied correctly even when the source is an indirect member lvalue.
+- Unsigned one-word division and remainder, carried as distinct `UDIV` and
+ `UREM` P1 operations on every backend rather than inheriting signed `DIV`
+ and `REM` behavior.
+- Local nested aggregate initialization distinguishes a compatible
+ whole-aggregate assignment expression from brace elision, including
+ designated compound literals.
+- Unevaluated-expression snapshots restore frame allocation, indirect-slot,
+ label, outgoing-call, emission-buffer, and value-stack state, so `sizeof`
+ cannot alter a later local's storage.
+- An immediate string literal retains its array bound under `sizeof`, making
+ local `KIT_SLICE_LIT` lengths agree with their file-scope equivalents.
+- Block-scope static objects use a per-function declaration identity in their
+ emitted label. Distinct lexical `order` tables no longer collapse merely
+ because they share a source spelling.
+- Complete `--lib` translation-unit namespacing for static objects/functions,
+ strings, frame slots, user labels, and generated labels. This makes P1pp
+ fragments independently compilable and safely concatenable.
+- A 512 KiB reusable per-function emission buffer on 64-bit targets, sized
+ for Kit's largest linker-layout routine while preserving cc.scm's
+ fixed-allocation model and RV32's existing 256 KiB heap budget.
+- A 32-word variadic receive window (with ABI-word-aware bounds checks),
+ covering Kit's 20-argument linker diagnostics without changing the existing
+ four-register/stack-argument convention.
+
+The intentionally bootstrap-level behavior is documented in `docs/CC.md`:
+`_Noreturn` carries no metadata, and `_Alignas` accepts only the universally
+layout-neutral zero or one/`char` requests because P1's seed object layout
+cannot represent over-alignment. Kit's prepared implementation TUs contain no
+live `_Alignas` declarations; the focused fixture exercises this no-op
+contract.
+
+## Focused regression tests
+
+Run the compatibility cases across all four boot2 architectures:
+
+```sh
+tests/run.sh --suite cc \
+ 320-vararg-cap 346-c11-decl-align 347-aggregate-indirect-arg \
+ 348-unsigned-div-rem 349-aggregate-indirect-lvalue-arg \
+ 350-large-aggregate-flow 351-address-of-member-local \
+ 352-aggregate-compound-call 353-static-local-scope-collision
+tests/run.sh --suite cc-util 015-lib-static-namespace
+tests/run.sh --suite m1pp 032-reset-output
+```
+
+The first fixture exercises a 20-argument call above the former variadic
+window. The declaration fixture covers every added declaration/alignment form
+plus both `offsetof` contexts, nested designators, and grouped aggregate
+compound initialization. The aggregate fixture checks that a callee may
+mutate a large by-value argument without changing its source object. The
+additional aggregate fixtures cover indirect member lvalues and nested
+whole-aggregate flow. The arithmetic fixture checks unsigned quotient and
+remainder boundary cases. The address fixture protects the unevaluated
+snapshot invariant. The compound-call fixture checks both the two-word
+argument shape and local string-literal `sizeof`; the static-local fixture
+checks same-spelling declarations in distinct scopes. The utility fixture
+checks every non-function-local label family under `--lib`; the M1pp fixture
+checks output reset and hygiene-base selection.
+
+The Kit-side check then supplies the integration coverage: every selected TU
+must translate, the entire P1 set must link with mes-libc, the resulting Kit0
+must run for the selected architecture, and that seed-built compiler must emit
+and archive target objects and statically link a representative executable.
diff --git a/docs/M1PP.md b/docs/M1PP.md
@@ -24,6 +24,8 @@ The implementation lives in `M1pp/M1pp.c`. It is one pass, allocation-free
zero-arg macros plus `SIZE`/`COUNT`
- Named stack-frame access via `%frame` / `%endframe` + `%local(field)`,
composing with `%struct`-generated `<frame>_FRAME.<field>` macros
+- Partitioned expansion via `%reset-output BASE`, retaining definitions while
+ discarding a replayed prelude and assigning a disjoint hygiene-id range
- Compile-time target-word integer expression language (Lisp S-expressions:
arithmetic, bitwise, shift, comparison, `strlen`)
- Little-endian hex emission: `!` (1B), `@` (2B), `%` (4B), `$` (8B) —
@@ -120,6 +122,18 @@ synthesized by `%struct`). Frames do not nest: a second `%frame` before
end-of-input. `NAME` is a single `WORD` token and may come from
macro-argument substitution.
+### `%reset-output`
+
+ %reset-output BASE
+
+At line start, discards all output emitted so far, resets token spacing, and
+sets the macro-hygiene expansion counter to the nonnegative integer `BASE`.
+Macro definitions remain installed, so a large program can expand each
+translation unit independently by replaying a shared macro prelude, issuing a
+different reset base, and concatenating the resulting output partitions. The
+first macro call after the directive uses `BASE + 1`; callers must reserve
+non-overlapping ranges. `BASE` may not exceed 2147483646.
+
## Macro calls
%NAME(arg, arg, ...)
diff --git a/docs/P1.md b/docs/P1.md
@@ -249,7 +249,7 @@ Leaf functions that need no frame-local storage may omit the frame entirely.
|----------|------------|
| Materialization | `LI rd, imm`, `LA rd, %label`, `LA_BR %label` |
| Moves | `MOV rd, rs`, `MOV rd, sp` |
-| Arithmetic | `ADD`, `SUB`, `AND`, `OR`, `XOR`, `SHL`, `SHR`, `SAR`, `MUL`, `DIV`, `REM` |
+| Arithmetic | `ADD`, `SUB`, `AND`, `OR`, `XOR`, `SHL`, `SHR`, `SAR`, `MUL`, `DIV`, `REM`, `UDIV`, `UREM` |
| Immediate arithmetic | `ADDI`, `ANDI`, `ORI`, `SHLI`, `SHRI`, `SARI` |
| Memory | `LD`, `ST`, `LB`, `SB` |
| ABI access | `LDARG` |
@@ -403,7 +403,7 @@ P1 defines the following arithmetic and bitwise operations on one-word
values:
- register-register: `ADD`, `SUB`, `AND`, `OR`, `XOR`, `SHL`, `SHR`, `SAR`,
- `MUL`, `DIV`, `REM`
+ `MUL`, `DIV`, `REM`, `UDIV`, `UREM`
- immediate: `ADDI`, `ANDI`, `ORI`, `SHLI`, `SHRI`, `SARI`
For `ADD`, `SUB`, `MUL`, `AND`, `OR`, and `XOR`, computation is modulo the
@@ -419,11 +419,13 @@ Immediate-form shifts use inline immediates in the range `0..31` in `P1-32`
and `0..63` in `P1-64`.
`DIV` is signed division on one-word two's-complement values and truncates
-toward zero. `REM` is the corresponding signed remainder.
+toward zero. `REM` is the corresponding signed remainder. `UDIV` and `UREM`
+interpret both operands as unsigned one-word values and produce the unsigned
+quotient and remainder.
-Division by zero is outside the portable contract. The overflow case
-`MIN_INT / -1` is also outside the portable contract, as is the corresponding
-remainder case.
+Division by zero is outside the portable contract for all four operations.
+The overflow case `MIN_INT / -1` is outside the signed `DIV` contract, as is
+the corresponding signed `REM` case; it does not apply to `UDIV` or `UREM`.
### Moves
diff --git a/docs/TODO.md b/docs/TODO.md
@@ -0,0 +1,30 @@
+# Bootstrap TODO
+
+## TCC parity and `cc.scm` cleanup
+
+- [ ] Restore strict `tcc-cc` parity: every C construct accepted by `cc.scm`
+ must also be accepted by the bundled TCC.
+- [ ] Remove raw `_Static_assert`, `_Noreturn`, `_Alignas`, and C11 `_Alignof`
+ support from `cc.scm`; have the Kit bootstrap configuration erase or map
+ those spellings before either compiler sees them.
+- [ ] Replace Kit's `__builtin_offsetof` dependency with a portable `offsetof`
+ macro, then remove the builtin parser support.
+- [ ] Rewrite Kit's three nested-designator initializers and four file-scope
+ slice compound initializers, then remove their special handling and the
+ now-unused file-scope unevaluated-expression walker from `cc.scm`.
+- [ ] Retain only the ordered, non-overlapping array-designator behavior Kit
+ needs; keep the aggregate ABI, arithmetic, `sizeof`, namespacing, static-local,
+ function-buffer, and variadic correctness changes.
+- [ ] Rewrite or remove `346-c11-decl-align` and run the complete `cc` and
+ `tcc-cc` suites after the language subset is reduced.
+
+## Kit bootstrap profiles
+
+- [ ] Add a test that uses tcc to compile the kit0 TUs so that we reject
+ surviving unsupported C11/builtin tokens in Kit's generated translation
+ units.
+- [ ] Add a test that uses kit0 to compile TCC (so that each can compile the
+ other)
+- [ ] Add Kit `x64` and `rv64` bootstrap profiles matching boot2's `amd64` and
+ `riscv64` mappings, then run compile, link, smoke, and fixed-point checks for
+ all three 64-bit targets.
diff --git a/hex2pp/hex2pp.P1 b/hex2pp/hex2pp.P1
@@ -33,7 +33,7 @@
## --- Caps ------------------------------------------------------------------
## Mirrors hex2pp.c constants. Stored as 8-byte little-endian.
-DEFINE H2_INPUT_CAP 0000000100000000
+DEFINE H2_INPUT_CAP 0000000800000000
DEFINE H2_OUTPUT_CAP 0000000800000000
DEFINE H2_TEXT_CAP 0000800000000000
DEFINE H2_LABEL_CAP 0000100000000000
@@ -66,7 +66,7 @@ DEFINE ZERO4 '00000000'
## pat_buf = 4096 B
## ev_bytes = 8 B
## df_byte = 8 B
-## input_buf = 16 MiB
+## input_buf = 128 MiB
## output_buf = 128 MiB
## text_buf = 8 MiB
## labels = 32 MiB (1<<20 * 32 B)
@@ -82,18 +82,18 @@ DEFINE H2_OFF_other_buf 4021000000000000
DEFINE H2_OFF_pat_buf 4031000000000000
DEFINE H2_OFF_ev_bytes 4041000000000000
DEFINE H2_OFF_df_byte 5041000000000000
-## input_buf: 0x00004200 + 16 MiB
-## output_buf: 0x01004200 + 128 MiB
-## text_buf: 0x09004200 + 8 MiB
-## labels: 0x09804200 + 32 MiB
-## fixups: 0x0B804200 + 96 MiB
-## scope_history: 0x11804200 + 32 MiB (end at ~313 MiB; ELF p_memsz is 512 MiB).
+## input_buf: 0x00004200 + 128 MiB
+## output_buf: 0x08004200 + 128 MiB
+## text_buf: 0x10004200 + 8 MiB
+## labels: 0x10804200 + 32 MiB
+## fixups: 0x12804200 + 96 MiB
+## scope_history: 0x18804200 + 32 MiB (end at ~424 MiB; ELF p_memsz is 512 MiB).
DEFINE H2_OFF_input_buf 0042000000000000
-DEFINE H2_OFF_output_buf 0042000100000000
-DEFINE H2_OFF_text_buf 0042000900000000
-DEFINE H2_OFF_labels 0042800900000000
-DEFINE H2_OFF_fixups 0042800B00000000
-DEFINE H2_OFF_scope_history 0042801100000000
+DEFINE H2_OFF_output_buf 0042000800000000
+DEFINE H2_OFF_text_buf 0042001000000000
+DEFINE H2_OFF_labels 0042801000000000
+DEFINE H2_OFF_fixups 0042801200000000
+DEFINE H2_OFF_scope_history 0042801800000000
## --- p1_main: argv parse -> load input -> two passes -> write -> exit ------
diff --git a/hex2pp/hex2pp.c b/hex2pp/hex2pp.c
@@ -28,7 +28,7 @@
#include <string.h>
#include <sys/stat.h>
-#define MAX_INPUT_BYTES (16 * 1024 * 1024)
+#define MAX_INPUT_BYTES (128 * 1024 * 1024)
#define MAX_OUTPUT_BYTES (128 * 1024 * 1024)
#define MAX_LABELS (1 << 20)
#define MAX_FIXUPS (1 << 20)
diff --git a/tcc/libc/aarch64/sys_stubs.S b/tcc/libc/aarch64/sys_stubs.S
@@ -13,8 +13,14 @@
* don't surface to callers.
*/
- .globl sys_read, sys_write, sys_close, sys_open
- .globl sys_lseek, sys_brk, sys_unlink, sys_exit
+ .globl sys_read
+ .globl sys_write
+ .globl sys_close
+ .globl sys_open
+ .globl sys_lseek
+ .globl sys_brk
+ .globl sys_unlink
+ .globl sys_exit
sys_read:
mov x8, #63
@@ -64,4 +70,5 @@ sys_exit:
mov x8, #93
svc #0
/* unreachable */
- b .
+.Lsys_exit_hang:
+ b .Lsys_exit_hang
diff --git a/tests/M1pp/032-reset-output.M1pp b/tests/M1pp/032-reset-output.M1pp
@@ -0,0 +1,10 @@
+# A partition can replay macro definitions and runtime text, then discard the
+# runtime text while retaining the macros. BASE gives its hygiene labels a
+# disjoint range from every other partition.
+discard me
+%macro AGAIN()
+ :@loop
+ jump &@loop
+%endm
+%reset-output 100
+%AGAIN() tail
diff --git a/tests/M1pp/032-reset-output.expected b/tests/M1pp/032-reset-output.expected
@@ -0,0 +1,3 @@
+:loop__101
+jump &loop__101
+tail
diff --git a/tests/cc-util/015-lib-static-namespace.expected b/tests/cc-util/015-lib-static-namespace.expected
@@ -0,0 +1,5 @@
+tu7__cc__state
+tu7__cc__helper
+(+ %tu7__cc__helper__SO 24)
+tu7__cc__helper__user_done
+tu7__cc__cl_0
diff --git a/tests/cc-util/015-lib-static-namespace.expected-exit b/tests/cc-util/015-lib-static-namespace.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc-util/015-lib-static-namespace.scm b/tests/cc-util/015-lib-static-namespace.scm
@@ -0,0 +1,18 @@
+;; --lib's prefix is a translation-unit namespace, not merely a string-literal
+;; namespace. Exercise every non-function-local label family directly.
+(let* ((cg (cg-init/v #t "tu7__"))
+ (sty (%ctype 'fn -1 -1 (list %t-i32 '() #f)))
+ (fn (%sym "helper" 'fn 'static sty #f #t))
+ (var (%sym "state" 'var 'static %t-i32 #f #t)))
+ (%hash-set! (car (world-scope (cg-world cg))) "helper" fn)
+ (write-bv-fd 1 (%cg-sym-label cg var))
+ (write-bv-fd 1 "\n")
+ (cg-fn-begin cg "helper" '() %t-i32)
+ (write-bv-fd 1 (%cg-fn-get cg '%fn-label))
+ (write-bv-fd 1 "\n")
+ (write-bv-fd 1 (%cg-slot-expr cg 24))
+ (write-bv-fd 1 "\n")
+ (write-bv-fd 1 (%cg-user-label cg "done"))
+ (write-bv-fd 1 "\n")
+ (write-bv-fd 1 (%cg-fresh-cl-label cg))
+ (write-bv-fd 1 "\n"))
diff --git a/tests/cc/320-vararg-cap.c b/tests/cc/320-vararg-cap.c
@@ -1,10 +1,10 @@
-/* Variadic save area is capped at 16 incoming-arg slots (named +
+/* Variadic save area is capped at 32 incoming-argument words (named +
* variadic). cg should reject calls that exceed this cap with a clear
* compile-time error rather than silently miscompile by writing past
* the save area in the callee's prologue.
*
- * This test exercises the boundary: 1 named + 15 variadic = 16 args
- * total, which is the maximum supported. Sum 1..15 = 120. */
+ * This test exercises the Kit-motivated range above the old 16-word cap:
+ * 1 named + 19 variadic = 20 args total. Sum 1..19 = 190. */
#ifndef CCSCM
#include <stdarg.h>
@@ -31,6 +31,7 @@ int sum(int n, ...) {
}
int main(void) {
- /* 15 variadic ints; n itself is named arg 0; total 16 args. */
- return sum(15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+ /* 19 variadic ints; n itself is named arg 0; total 20 args. */
+ return sum(19, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19);
}
diff --git a/tests/cc/320-vararg-cap.expected-exit b/tests/cc/320-vararg-cap.expected-exit
@@ -1 +1 @@
-120
+190
diff --git a/tests/cc/346-c11-decl-align.c b/tests/cc/346-c11-decl-align.c
@@ -0,0 +1,83 @@
+/* Small C11 declaration forms used by Kit's public/core headers. _Alignas
+ * is intentionally natural here: cc.scm's seed ABI accepts the spelling but
+ * does not promise over-aligned storage. */
+
+typedef struct Pair {
+ char c;
+ long value;
+ _Static_assert(sizeof(long) >= sizeof(char), "member assertion");
+} Pair;
+
+struct Nested {
+ struct {
+ int value;
+ } inner;
+};
+
+typedef struct Slice {
+ const char *text;
+ unsigned long len;
+} Slice;
+
+typedef struct NameSlice {
+ union {
+ const char *s;
+ const unsigned char *data;
+ };
+ unsigned long len;
+} NameSlice;
+
+typedef struct InsnDesc {
+ NameSlice name;
+ int code;
+} InsnDesc;
+
+#define SLICE(lit) ((Slice){ .text = (lit), .len = sizeof(lit) - 1 })
+#define NAME_SLICE(lit) \
+ ((NameSlice){ .s = (lit), .len = sizeof(lit) - 1 })
+
+_Static_assert(_Alignof(long) == sizeof(long), "natural scalar alignment");
+_Static_assert(_Alignof(Pair) >= _Alignof(long), "aggregate alignment");
+
+_Alignas(char) static long aligned_value = 9;
+static const unsigned seed_table[] = { 1, 2, 3 };
+static const unsigned designated_table[4] = { [2] = 13, [0] = 5 };
+static const struct Nested nested = { .inner.value = 11 };
+static const Slice slices[] = { SLICE("x"), SLICE("yz") };
+static const InsnDesc insns[] = {
+ {{{("movn")}, sizeof("movn") - 1}, 3},
+};
+static const InsnDesc compound_insns[] = {
+ {NAME_SLICE("movz"), 4},
+};
+
+_Static_assert(sizeof seed_table / sizeof seed_table[0] == 3,
+ "file-scope unevaluated array designator");
+_Static_assert(_Alignof(seed_table[0]) == _Alignof(unsigned),
+ "file-scope alignof expression");
+_Static_assert(__alignof__(unsigned) == _Alignof(unsigned),
+ "GNU alignof alias");
+_Static_assert(__builtin_offsetof(Pair, value) == _Alignof(long),
+ "builtin offsetof uses aggregate layout");
+_Static_assert(sizeof(((Pair *)0)->value) == sizeof(long),
+ "file-scope sizeof handles a cast designator");
+
+_Noreturn static void seed_noreturn_spelling(int *out)
+{
+ *out = 7;
+}
+
+int main(void)
+{
+ int result = 0;
+ _Static_assert(_Alignof(Pair) >= 1, "block assertion");
+ seed_noreturn_spelling(&result);
+ return result + (aligned_value != 9) + (nested.inner.value != 11) +
+ (slices[0].len != 1) + (slices[1].text[0] != 'y') +
+ (designated_table[0] != 5) + (designated_table[1] != 0) +
+ (designated_table[2] != 13) + (designated_table[3] != 0) +
+ (insns[0].name.s[0] != 'm') + (insns[0].code != 3) +
+ (compound_insns[0].name.s[3] != 'z') +
+ (compound_insns[0].code != 4) +
+ (__builtin_offsetof(Pair, value) != _Alignof(long));
+}
diff --git a/tests/cc/346-c11-decl-align.expected-exit b/tests/cc/346-c11-decl-align.expected-exit
@@ -0,0 +1 @@
+7
diff --git a/tests/cc/347-aggregate-indirect-arg.c b/tests/cc/347-aggregate-indirect-arg.c
@@ -0,0 +1,33 @@
+/* Aggregates wider than two target words are passed through a pointer to a
+ * caller-owned copy. The callee may mutate its by-value parameter without
+ * changing the source object. */
+struct BigArg {
+ long a;
+ long b;
+ long c;
+};
+
+static long consume(struct BigArg value, long bias)
+{
+ long sum = value.a + value.b + value.c + bias;
+ value.a = 99;
+ return sum;
+}
+
+static long consume_after(long bias, struct BigArg value)
+{
+ value.b = 20;
+ return bias + value.a + value.b + value.c;
+}
+
+int main(void)
+{
+ struct BigArg value = { 1, 2, 3 };
+ long got = consume(value, 4);
+ if (got != 10) return 1;
+ if (value.a != 1) return 2;
+ got = consume_after(4, value);
+ if (got != 28) return 3;
+ if (value.b != 2) return 4;
+ return 0;
+}
diff --git a/tests/cc/347-aggregate-indirect-arg.expected-exit b/tests/cc/347-aggregate-indirect-arg.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/348-unsigned-div-rem.c b/tests/cc/348-unsigned-div-rem.c
@@ -0,0 +1,14 @@
+int main(void) {
+ unsigned long all = ~0UL;
+ unsigned int word = ~0U;
+
+ if (sizeof all == 8) {
+ if (all / 8UL != 2305843009213693951UL) return 1;
+ } else if (all / 8UL != 536870911UL) {
+ return 1;
+ }
+ if (all % 10UL != 5UL) return 2;
+ if (word / 3U != 1431655765U) return 3;
+ if (word % 7U != 3U) return 4;
+ return 0;
+}
diff --git a/tests/cc/348-unsigned-div-rem.expected-exit b/tests/cc/348-unsigned-div-rem.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/349-aggregate-indirect-lvalue-arg.c b/tests/cc/349-aggregate-indirect-lvalue-arg.c
@@ -0,0 +1,30 @@
+/* A small aggregate passed from an indirect lvalue must contribute its
+ * contents to the ABI argument slot, not the address of the lvalue. This is
+ * the shape produced by `p->loc` throughout Kit's preprocessor. */
+struct LocRef {
+ unsigned file_id;
+ unsigned off;
+};
+
+struct Token {
+ unsigned kind;
+ struct LocRef loc;
+};
+
+static int check(struct LocRef loc)
+{
+ if (loc.file_id != 17) return 1;
+ if (loc.off != 29) return 2;
+ return 0;
+}
+
+static int check_token(struct Token *token)
+{
+ return check(token->loc);
+}
+
+int main(void)
+{
+ struct Token token = { 3, { 17, 29 } };
+ return check_token(&token);
+}
diff --git a/tests/cc/349-aggregate-indirect-lvalue-arg.expected-exit b/tests/cc/349-aggregate-indirect-lvalue-arg.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/350-large-aggregate-flow.c b/tests/cc/350-large-aggregate-flow.c
@@ -0,0 +1,125 @@
+/* Exercise the large-aggregate path Kit's ApiSValue stack uses: construct a
+ * 64-byte value, pass it by value into a stack push, return it indirectly from
+ * a pop, and initialize another local from that return value. */
+struct Value {
+ unsigned long a;
+ unsigned long b;
+ unsigned long c;
+ unsigned long d;
+ unsigned long e;
+ unsigned long f;
+ unsigned long g;
+ unsigned long h;
+};
+
+struct Stack {
+ struct Value values[2];
+ unsigned n;
+};
+
+struct Operand {
+ unsigned long x;
+ unsigned long y;
+ unsigned long z;
+};
+
+struct Wrapped {
+ struct Operand operand;
+ unsigned long d;
+ unsigned long e;
+ unsigned long f;
+ unsigned long g;
+ unsigned long h;
+};
+
+static struct Operand make_operand(unsigned long base)
+{
+ struct Operand operand;
+ operand.x = base;
+ operand.y = base + 1;
+ operand.z = base + 2;
+ return operand;
+}
+
+static struct Wrapped wrap_operand(struct Operand operand,
+ unsigned long tail)
+{
+ return (struct Wrapped){
+ .operand = operand,
+ .d = tail,
+ .e = tail + 1,
+ .f = tail + 2,
+ .g = tail + 3,
+ .h = tail + 4,
+ };
+}
+
+static struct Value make_value(unsigned long base)
+{
+ struct Value value;
+ value.a = base + 0;
+ value.b = base + 1;
+ value.c = base + 2;
+ value.d = base + 3;
+ value.e = base + 4;
+ value.f = base + 5;
+ value.g = base + 6;
+ value.h = base + 7;
+ return value;
+}
+
+static struct Value make_value_from_operand(struct Operand operand,
+ unsigned long tail)
+{
+ struct Value value;
+ value.a = operand.x;
+ value.b = operand.y;
+ value.c = operand.z;
+ value.d = tail;
+ value.e = tail + 1;
+ value.f = tail + 2;
+ value.g = tail + 3;
+ value.h = tail + 4;
+ return value;
+}
+
+static void push(struct Stack *stack, struct Value value)
+{
+ stack->values[stack->n++] = value;
+}
+
+static struct Value pop(struct Stack *stack)
+{
+ return stack->values[--stack->n];
+}
+
+int main(void)
+{
+ struct Stack stack = {0};
+ struct Value value;
+ struct Wrapped wrapped = wrap_operand(make_operand(80), 83);
+ if (wrapped.operand.x != 80) return 11;
+ if (wrapped.operand.y != 81) return 12;
+ if (wrapped.operand.z != 82) return 13;
+ if (wrapped.d != 83 || wrapped.h != 87) return 14;
+ push(&stack, make_value(32));
+ push(&stack, make_value_from_operand(make_operand(64), 67));
+ {
+ struct Value tmp = stack.values[0];
+ stack.values[0] = stack.values[1];
+ stack.values[1] = tmp;
+ }
+ value = pop(&stack);
+ if (value.a != 32) return 1;
+ if (value.b != 33) return 2;
+ if (value.c != 34) return 3;
+ if (value.d != 35) return 4;
+ if (value.e != 36) return 5;
+ if (value.f != 37) return 6;
+ if (value.g != 38) return 7;
+ if (value.h != 39) return 8;
+ value = pop(&stack);
+ if (value.a != 64) return 9;
+ if (value.h != 71) return 10;
+ return 0;
+}
diff --git a/tests/cc/350-large-aggregate-flow.expected-exit b/tests/cc/350-large-aggregate-flow.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/351-address-of-member-local.c b/tests/cc/351-address-of-member-local.c
@@ -0,0 +1,28 @@
+typedef unsigned long ulong;
+
+struct Inner {
+ ulong first;
+ ulong second;
+};
+
+struct Outer {
+ ulong prefix;
+ struct Inner inner;
+ ulong suffix;
+};
+
+static struct Outer storage;
+
+int main(void) {
+ struct Outer* outer = &storage;
+ ulong outer_size = sizeof *outer;
+ struct Inner* inner = &outer->inner;
+
+ if (outer_size != sizeof(struct Outer)) return 4;
+ inner->first = 0x11223344ul;
+ inner->second = 0x55667788ul;
+ if (storage.inner.first != 0x11223344ul) return 1;
+ if (storage.inner.second != 0x55667788ul) return 2;
+ if (inner != &storage.inner) return 3;
+ return 0;
+}
diff --git a/tests/cc/351-address-of-member-local.expected-exit b/tests/cc/351-address-of-member-local.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/352-aggregate-compound-call.c b/tests/cc/352-aggregate-compound-call.c
@@ -0,0 +1,30 @@
+/* A two-word compound literal passed directly by value must populate both
+ * ABI argument words. Kit uses this shape for KIT_SLICE_LIT in parser setup. */
+struct Slice {
+ const char *text;
+ unsigned long len;
+};
+
+static unsigned long inspect(struct Slice slice)
+{
+ if (!slice.text) return 1;
+ if (slice.text[0] != 'a') return 2;
+ if (slice.text[1] != 'b') return 3;
+ if (slice.text[2] != 'c') return 4;
+ if (slice.len != 3) return 5;
+ return 0;
+}
+
+int main(void)
+{
+ struct Slice slice = (struct Slice){
+ .text = "abc",
+ .len = sizeof("abc") - 1,
+ };
+ if (slice.len != 3) return (int)(10 + slice.len);
+ if (inspect(slice) != 0) return 7;
+ return (int)inspect((struct Slice){
+ .text = "abc",
+ .len = sizeof("abc") - 1,
+ });
+}
diff --git a/tests/cc/352-aggregate-compound-call.expected-exit b/tests/cc/352-aggregate-compound-call.expected-exit
@@ -0,0 +1 @@
+0
diff --git a/tests/cc/353-static-local-scope-collision.c b/tests/cc/353-static-local-scope-collision.c
@@ -0,0 +1,25 @@
+/* Distinct lexical scopes in one function may reuse a block-scope static
+ * identifier. Each declaration has its own object and emitted label. */
+static int pick(int which)
+{
+ if (which == 0) {
+ static const int order[] = {12};
+ return order[0];
+ }
+ if (which == 1) {
+ static const int order[] = {7, 9, 11};
+ return order[0];
+ }
+ {
+ static const int order[] = {3, 5};
+ return order[1];
+ }
+}
+
+int main(void)
+{
+ if (pick(0) != 12) return 1;
+ if (pick(1) != 7) return 2;
+ if (pick(2) != 5) return 3;
+ return 0;
+}
diff --git a/tests/cc/353-static-local-scope-collision.expected-exit b/tests/cc/353-static-local-scope-collision.expected-exit
@@ -0,0 +1 @@
+0