From 961cfb786df75ea7ea9065b913ff106020c16b64 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Sat, 4 Jul 2026 21:15:42 +0300 Subject: [PATCH] =?UTF-8?q?toolchain:=20gsinit=20=D0=B7=D0=B0=D0=BD=D1=83?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=5FDATA=20(C-=D1=81=D0=B5=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=D0=B8=D0=BA=D0=B0=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=B2)=20+=20sprinter-cc=20--max-allocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Все четыре crt0 (default/small/minimal/banked): gsinit теперь зануляет _DATA и _BSS через общий zero_area, затем копирует _INITIALIZER. Явные `= 0` у глобалов/статиков больше не нужны (они жгли байты _INITIALIZER в образе). crt0-приватные переменные, записываемые ДО gsinit (_estex_startup_ix и др.), перенесены из _DATA в _CODE (RAM). sprinter-cc: новая опция --max-allocs N → SDCC --max-allocs-per-node (агрессивнее аллокация регистров, меньше/быстрее код ценой времени компиляции). Co-Authored-By: Claude Fable 5 --- bin/sprinter-cc | 6 +++++ runtime/crt0.s | 46 +++++++++++++++++++++++------------ runtime/crt0_banked.s | 47 ++++++++++++++++++++++++------------ runtime/crt0_minimal.s | 47 +++++++++++++++++++++++------------- runtime/crt0_small.s | 54 +++++++++++++++++++++++++++--------------- 5 files changed, 134 insertions(+), 66 deletions(-) diff --git a/bin/sprinter-cc b/bin/sprinter-cc index a5071e4..fe9f549 100755 --- a/bin/sprinter-cc +++ b/bin/sprinter-cc @@ -34,6 +34,9 @@ # --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked # automatically and adds -Wl-b_BANKN=0x{N}C000 # --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0) +# --max-allocs N SDCC --max-allocs-per-node (default: SDCC's 3000). +# Higher values give smaller/faster code at the cost of +# compile time; 10000..100000 is a reasonable range. # --debug enable runtime diagnostics — defines DEBUG_RT for both # sdcc (-DDEBUG_RT) and the crt0 assembly (prepended # `DEBUG_RT = 1`). Exposes runtime introspection symbols @@ -82,6 +85,7 @@ SOURCES=() LD_EXTRA=() MKEXE_EXTRA=() BANK_SPECS=() # entries like "1=engine.c" +MAX_ALLOCS="" # if set, passed to sdcc as --max-allocs-per-node # ------- Parse args ---------------------------------------------------------- usage() { @@ -105,6 +109,7 @@ while [[ $# -gt 0 ]]; do -Wl) LD_EXTRA+=("$2"); shift 2;; --bank) BANK_SPECS+=("$2"); shift 2;; --mkexe) MKEXE_EXTRA+=("$2"); shift 2;; + --max-allocs) MAX_ALLOCS="$2"; shift 2;; --debug) DEBUG_RT=1; shift;; -v) VERBOSE=1; shift;; -h|--help) usage 0;; @@ -272,6 +277,7 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC" USER_RELS=() CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}") [[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT) +[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS") for src in "${SOURCES[@]}"; do rel="$WORK/$(basename "$src" .c).rel" run "$SDCC" "${CC_FLAGS[@]}" -c -o "$rel" "$src" diff --git a/runtime/crt0.s b/runtime/crt0.s index 933bf37..4146c4a 100644 --- a/runtime/crt0.s +++ b/runtime/crt0.s @@ -27,6 +27,8 @@ .globl s__INITIALIZER .globl l__INITIALIZER .globl s__INITIALIZED + .globl s__DATA + .globl l__DATA .globl s__BSS .globl l__BSS @@ -300,10 +302,13 @@ gpn_skip: ;; ========================================================================= ;; Runtime data (HOME's _DATA / _BSS) ;; ========================================================================= - .area _DATA + .area _CODE +;; Written BEFORE gsinit runs (gsinit zeroes _DATA), so these live in +;; _CODE — RAM on the Sprinter, always mapped. _estex_startup_ix:: .ds 2 + .area _DATA _argc:: .ds 2 _argv:: @@ -326,32 +331,43 @@ progname_buf: .ds 128 ; ESTEX APPINFO app_path target ;; ========================================================================= -;; gsinit — copy _INITIALIZER -> _INITIALIZED, then zero _BSS. +;; gsinit — zero _DATA + _BSS, then copy _INITIALIZER -> _INITIALIZED. +;; _DATA is zeroed because C guarantees zero-valued statics — this lets +;; code drop explicit `= 0` initializers that burn _INITIALIZER bytes. ;; ========================================================================= .area _GSINIT -gsinit:: - ld bc, #l__INITIALIZER + .area _CODE +;; zero_area — fill BC bytes at HL with 0 (BC may be zero). +zero_area: ld a, b or a, c - jr Z, gsinit_bss - ld de, #s__INITIALIZED - ld hl, #s__INITIALIZER - ldir -gsinit_bss: - ld bc, #l__BSS - ld a, b - or a, c - jr Z, gsinit_done - ld hl, #s__BSS + ret Z ld (hl), #0 dec bc ld a, b or a, c - jr Z, gsinit_done + ret Z ld d, h ld e, l inc de ldir + ret + + .area _GSINIT +gsinit:: + ld hl, #s__DATA + ld bc, #l__DATA + call zero_area + ld hl, #s__BSS + ld bc, #l__BSS + call zero_area + ld bc, #l__INITIALIZER + ld a, b + or a, c + jr Z, gsinit_done + ld de, #s__INITIALIZED + ld hl, #s__INITIALIZER + ldir gsinit_done: .area _GSFINAL diff --git a/runtime/crt0_banked.s b/runtime/crt0_banked.s index 9749aa0..aa4df32 100644 --- a/runtime/crt0_banked.s +++ b/runtime/crt0_banked.s @@ -35,6 +35,8 @@ .globl s__INITIALIZER .globl l__INITIALIZER .globl s__INITIALIZED + .globl s__DATA + .globl l__DATA .globl s__BSS .globl l__BSS @@ -398,7 +400,9 @@ gpn_skip: ;; ========================================================================= ;; Runtime data ;; ========================================================================= - .area _DATA + .area _CODE +;; Written BEFORE gsinit runs (gsinit zeroes _DATA), so these live in +;; _CODE — RAM on the Sprinter, always mapped. _estex_startup_ix:: .ds 2 _estex_file_handle:: @@ -407,6 +411,8 @@ _estex_block_id:: .ds 1 _bank_block_id:: .ds 1 + + .area _DATA _argc:: .ds 2 _argv:: @@ -427,32 +433,43 @@ _w2_self_allocated:: .endif ;; ========================================================================= -;; gsinit — same as crt0.s (copy INITIALIZER, zero BSS) +;; gsinit — same as crt0.s (zero _DATA + _BSS, copy INITIALIZER) ;; ========================================================================= .area _GSINIT -gsinit:: - ld bc, #l__INITIALIZER + .area _CODE +;; zero_area — fill BC bytes at HL with 0 (BC may be zero). +zero_area: ld a, b or a, c - jr Z, gsinit_bss - ld de, #s__INITIALIZED - ld hl, #s__INITIALIZER - ldir -gsinit_bss: - ld bc, #l__BSS - ld a, b - or a, c - jr Z, gsinit_done - ld hl, #s__BSS + ret Z ld (hl), #0 dec bc ld a, b or a, c - jr Z, gsinit_done + ret Z ld d, h ld e, l inc de ldir + ret + + .area _GSINIT +gsinit:: + ;; --- Zero _DATA + _BSS (C guarantees zero-valued statics) --- + ld hl, #s__DATA + ld bc, #l__DATA + call zero_area + ld hl, #s__BSS + ld bc, #l__BSS + call zero_area + ;; --- Copy _INITIALIZER -> _INITIALIZED if non-empty --- + ld bc, #l__INITIALIZER + ld a, b + or a, c + jr Z, gsinit_done + ld de, #s__INITIALIZED + ld hl, #s__INITIALIZER + ldir gsinit_done: .area _GSFINAL diff --git a/runtime/crt0_minimal.s b/runtime/crt0_minimal.s index 37e7f0b..9951d88 100644 --- a/runtime/crt0_minimal.s +++ b/runtime/crt0_minimal.s @@ -20,6 +20,8 @@ .globl s__INITIALIZER .globl l__INITIALIZER .globl s__INITIALIZED + .globl s__DATA + .globl l__DATA .globl s__BSS .globl l__BSS @@ -70,40 +72,51 @@ _start:: ;; ========================================================================= ;; Runtime data ;; ========================================================================= - .area _DATA + .area _CODE +;; Written BEFORE gsinit runs (gsinit zeroes _DATA), so these live in +;; _CODE — RAM on the Sprinter, always mapped. _estex_startup_ix:: .ds 2 ;; ========================================================================= -;; gsinit — copy _INITIALIZER -> _INITIALIZED, then zero _BSS. +;; gsinit — zero _DATA + _BSS, then copy _INITIALIZER -> _INITIALIZED. ;; SDCC appends per-unit init code between this label and the ret in _GSFINAL. ;; ========================================================================= .area _GSINIT -gsinit:: - ;; --- Copy _INITIALIZER -> _INITIALIZED if non-empty --- - ld bc, #l__INITIALIZER + .area _CODE +;; zero_area — fill BC bytes at HL with 0 (BC may be zero). +zero_area: ld a, b or a, c - jr Z, gsinit_bss - ld de, #s__INITIALIZED - ld hl, #s__INITIALIZER - ldir -gsinit_bss: - ;; --- Zero _BSS if non-empty --- - ld bc, #l__BSS - ld a, b - or a, c - jr Z, gsinit_done - ld hl, #s__BSS + ret Z ld (hl), #0 dec bc ld a, b or a, c - jr Z, gsinit_done + ret Z ld d, h ld e, l inc de ldir + ret + + .area _GSINIT +gsinit:: + ;; --- Zero _DATA + _BSS (C guarantees zero-valued statics) --- + ld hl, #s__DATA + ld bc, #l__DATA + call zero_area + ld hl, #s__BSS + ld bc, #l__BSS + call zero_area + ;; --- Copy _INITIALIZER -> _INITIALIZED if non-empty --- + ld bc, #l__INITIALIZER + ld a, b + or a, c + jr Z, gsinit_done + ld de, #s__INITIALIZED + ld hl, #s__INITIALIZER + ldir gsinit_done: .area _GSFINAL diff --git a/runtime/crt0_small.s b/runtime/crt0_small.s index 14c6185..b194721 100644 --- a/runtime/crt0_small.s +++ b/runtime/crt0_small.s @@ -38,6 +38,8 @@ .globl s__INITIALIZER .globl l__INITIALIZER .globl s__INITIALIZED + .globl s__DATA + .globl l__DATA .globl s__BSS .globl l__BSS @@ -119,11 +121,11 @@ w2_join: ld sp, #0xBFFE .ifdef DEBUG_RT ;; Save the self-alloc flag across gsinit (which clobbers BC/DE/HL and - ;; zeroes BSS). SP is in W2 now, push is safe. + ;; zeroes _DATA/_BSS). SP is in W2 now, push is safe. push af .endif - ;; ----- Step 5: save IX prefix in its canonical W2 slot. ------------- + ;; ----- Step 5: save IX prefix in its canonical slot (_CODE, W1). ---- ld (_estex_startup_ix), ix ;; ----- Step 6: standard gsinit + argv flow (identical to crt0.s). -- @@ -336,10 +338,13 @@ gpn_skip: ;; Runtime data — same layout as crt0.s. Lives in DATA/BSS which the ;; linker places in W2 (--data-loc 0x8000). ;; ========================================================================= - .area _DATA + .area _CODE +;; _estex_startup_ix is written BEFORE gsinit runs (gsinit zeroes _DATA), +;; so it lives in _CODE — RAM on the Sprinter, always mapped. _estex_startup_ix:: .ds 2 + .area _DATA _argc:: .ds 2 _argv:: @@ -362,33 +367,44 @@ _w2_self_allocated:: .endif ;; ========================================================================= -;; gsinit — copy _INITIALIZER -> _INITIALIZED, then zero _BSS. Identical -;; to crt0.s; runs AFTER W2 is mapped so writes to _INITIALIZED/_BSS land. +;; gsinit — zero _DATA + _BSS, then copy _INITIALIZER -> _INITIALIZED. +;; Identical to crt0.s; runs AFTER W2 is mapped so the writes land. +;; _DATA is zeroed because C guarantees zero-valued statics — initializing +;; `static uint8_t x;` here lets code drop explicit `= 0` initializers +;; that would otherwise burn _INITIALIZER bytes in the EXE image. ;; ========================================================================= - .area _GSINIT -gsinit:: - ld bc, #l__INITIALIZER + .area _CODE +;; zero_area — fill BC bytes at HL with 0 (BC may be zero). +zero_area: ld a, b or a, c - jr Z, gsinit_bss - ld de, #s__INITIALIZED - ld hl, #s__INITIALIZER - ldir -gsinit_bss: - ld bc, #l__BSS - ld a, b - or a, c - jr Z, gsinit_done - ld hl, #s__BSS + ret Z ld (hl), #0 dec bc ld a, b or a, c - jr Z, gsinit_done + ret Z ld d, h ld e, l inc de ldir + ret + + .area _GSINIT +gsinit:: + ld hl, #s__DATA + ld bc, #l__DATA + call zero_area + ld hl, #s__BSS + ld bc, #l__BSS + call zero_area + ld bc, #l__INITIALIZER + ld a, b + or a, c + jr Z, gsinit_done + ld de, #s__INITIALIZED + ld hl, #s__INITIALIZER + ldir gsinit_done: .area _GSFINAL