diff --git a/CLAUDE.md b/CLAUDE.md index ae6d77b..f3b2158 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,13 +7,17 @@ Target-слой SDCC 4.5 (z80) для компьютера Sprinter Sp2000: crt0 ``` make # tools + lib + libbgi + все тесты (45) + examples -make -C libc # только libc → lib/sprinter.lib -make -C libbgi # только BGI → lib/bgi256.lib (и bgi16.lib в Фазе 2) +make -C libc # только libc → lib/sprinter.lib (fast) + sprinter_safe.lib +make -C libbgi # только BGI → lib/bgi256.lib (fast) + bgi256_safe.lib make floppy # упаковать все .exe в mame/v306/IMG/mc.img make size-check # размерный регресс: _CODE vs docs/size_baseline.tsv make size-baseline # принять текущие размеры эталоном ``` +Обе библиотеки собираются в двух вариантах: fast (дефолт; `-D*_NOCHECK` — +параметр-валидации вырезаны) и safe (линкуется по `sprinter-cc --safe`). +Критичные гарды (напр. _fd_guard — 9-й OPEN вешает DSS) — в ОБОИХ. + Графика (BGI) — отдельная библиотека libbgi/ (см. ниже). Программа, использующая graphics.h, собирается с `--gfx 256` (или `--gfx 16` в Фазе 2): sprinter-cc подлинкует lib/bgi256.lib и добавит -I libbgi/include. diff --git a/Makefile b/Makefile index 6ef56cf..feba83a 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,9 @@ TESTS := hello hello2 simple banked bankedbg banktest strtest cat seek \ malloc mem_test argv errno rt_test openenv ls conio conio2 \ attrprob timedir mouse banklocl stdlib assrtest ptime stattest \ - filetest fdmax fbench solidt irqtest cbltest cblwav dec_test gets stest2 winrest \ + filetest fdmax fbench solidt irqtest cbltest cblwav cblstream dec_test gets stest2 winrest \ bios_text text_palette \ - gfx_demo gfx_dbuf + gfx_demo gfx_dbuf bgitest bgi_img accfill # gfx_d16 / gfx_text / gfx_mous — 16-цветные; убраны до Фазы 2 (bgi16.lib # ещё не собирается). Вернуть мигрированными на BGI --gfx 16. # Larger end-user applications under examples/. diff --git a/bin/sprinter-cc b/bin/sprinter-cc index 9721515..2c5ecd6 100755 --- a/bin/sprinter-cc +++ b/bin/sprinter-cc @@ -146,6 +146,15 @@ case "$GFX_MODE" in *) echo "sprinter-cc: --gfx: ожидается 256 (или 16), дано: $GFX_MODE" >&2; exit 1;; esac +# ------- libc variant selection (--safe) -------------------------------------- +# sprinter.lib — fast (LIBC_NOCHECK: параметр-валидации вырезаны); +# sprinter_safe.lib — с валидациями. Критичные гарды (_fd_guard) — в обеих. +LIBC_LD="-lsprinter" +if [[ "$SAFE_LIBS" -eq 1 ]] && [[ -f "$LIB_DIR/sprinter_safe.lib" ]]; then + LIBC_LD="-lsprinter_safe" + [[ $VERBOSE -eq 1 ]] && echo " libc: safe variant (lib/sprinter_safe.lib)" +fi + # ------- Resolve memory mode → CODE_LOC / DATA_LOC --------------------------- # tiny : CODE in W2 (0x8100), DATA auto after code (= W2) # small : CODE in W1 (0x4100), DATA in W2 (0x8000) — crt0 must alloc W2 @@ -367,12 +376,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done if [[ $VERBOSE -eq 1 ]]; then run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \ "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \ - "-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter" + "-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" else # Drop the warning line + its two follow-up "Library:" lines. run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \ "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \ - "-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter" 2>&1 \ + "-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" 2>&1 \ | awk ' /^\?ASlink-Warning-Definition of public symbol/ { skip = 3 } skip > 0 { skip--; next } diff --git a/docs/size_baseline.tsv b/docs/size_baseline.tsv index 3870fe4..722c599 100644 --- a/docs/size_baseline.tsv +++ b/docs/size_baseline.tsv @@ -1,44 +1,48 @@ # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update +accfill 3963 argv 3431 assrtest 3847 -attrprob 4091 +attrprob 4088 banked 1056 bankedbg 1067 banklocl 4832 banktest 3767 -bios_text 4463 +bgi_img 6572 +bgitest 4053 +bios_text 4460 cat 915 +cblstream 6277 cbltest 6407 cblwav 6486 -conio 4634 -conio2 3957 +conio 4631 +conio2 3954 dec_test 860 -errno 6216 -fbench 9040 -fdmax 6337 -filetest 11624 +errno 6145 +fbench 8656 +fdmax 6328 +filetest 11143 gets 509 gfx_dbuf 5378 gfx_demo 4286 -hello 4170 -hello2 4247 +hello 4167 +hello2 4244 irqtest 5881 -ls 4919 +ls 4916 malloc 4462 -mdview 19118 -mdview2 27036 +mdview 19115 +mdview2 27033 mem_test 4558 -mouse 4387 +mouse 4384 openenv 6127 ptime 5896 rt_test 4906 seek 4177 -simple 963 -solidt 12287 +simple 960 +solidt 12107 stattest 7700 stdlib 6643 stest2 3644 strtest 1340 -text_palette 5039 +text_palette 5036 timedir 5471 winrest 4479 diff --git a/libc/Makefile b/libc/Makefile index 501beec..2e34ecd 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -1,4 +1,4 @@ -# Build lib/sprinter.lib — the Sprinter target libc archive. +# Build lib/sprinter.lib + lib/sprinter_safe.lib — the Sprinter libc. # # libc/ — ядро библиотеки (БЕЗ графики; графика вынесена в libbgi/). # Каждый .c под libc// — отдельный .rel в архиве (1 публичная @@ -7,11 +7,18 @@ # (heap для malloc). crt0-семейство НЕ в архиве — оно всегда явно # линкуется, не DCE-eligible. См. docs/libc-split-asm-cases.md. # -# make build lib/sprinter.lib -# make clean remove build/ + lib/sprinter.lib +# Две версии (симметрично libbgi: bgi256.lib / bgi256_safe.lib): +# sprinter.lib = fast: -DLIBC_NOCHECK — параметр-валидации +# (NULL fp/buf, EBADF на неверное направление +# потока) ВЫРЕЗАНЫ; дефолт sprinter-cc. +# sprinter_safe.lib = safe: валидации включены; линкуется по --safe. +# КРИТИЧНЫЕ гарды (например _fd_guard — 9-й OPEN вешает DSS) живут в +# ОБЕИХ версиях и под LIBC_NOCHECK не попадают. # -# Симметрично libbgi/Makefile: исходники и build/ рядом, результат -# ложится в общий каталог lib/ (откуда его линкует sprinter-cc). +# make build обе библиотеки +# make fast только lib/sprinter.lib +# make safe только lib/sprinter_safe.lib +# make clean remove build/ + обе библиотеки PROJ_ROOT := $(abspath $(CURDIR)/..) SDCC_BIN := $(PROJ_ROOT)/third_party/sdcc/bin @@ -35,55 +42,73 @@ LIBC_C := $(wildcard $(CURDIR)/*/*.c) # ассемблирует bank.s с нужным BANK_W1 флагом на каждый banked-build. RUNTIME_S := $(PROJ_ROOT)/runtime/heap.s -LIBC_RELS := $(patsubst $(CURDIR)/%.c,$(BUILD)/%.rel,$(LIBC_C)) -RUNTIME_RELS := $(patsubst $(PROJ_ROOT)/runtime/%.s,$(BUILD)/%.rel,$(RUNTIME_S)) +FAST_RELS := $(patsubst $(CURDIR)/%.c,$(BUILD)/fast/%.rel,$(LIBC_C)) \ + $(patsubst $(PROJ_ROOT)/runtime/%.s,$(BUILD)/fast/%.rel,$(RUNTIME_S)) +SAFE_RELS := $(patsubst $(CURDIR)/%.c,$(BUILD)/safe/%.rel,$(LIBC_C)) \ + $(patsubst $(PROJ_ROOT)/runtime/%.s,$(BUILD)/safe/%.rel,$(RUNTIME_S)) -ALL_RELS := $(LIBC_RELS) $(RUNTIME_RELS) -KNOWN_RELS := $(ALL_RELS) +KNOWN_RELS := $(FAST_RELS) $(SAFE_RELS) # Штамп списка модулей: обновляется при любом изменении состава # (добавление/удаление/переименование исходника) — иначе удаление # исходника не триггерит перелинковку и stale .rel остаётся в архиве. -# Сразу сносим и архив: на exFAT (SAM8) гранулярность mtime грубая, +# Сразу сносим и архивы: на exFAT (SAM8) гранулярность mtime грубая, # и «список изменился» через сравнение времён можно проглядеть. MODLIST := $(BUILD)/.modules ifneq ($(shell cat $(MODLIST) 2>/dev/null),$(KNOWN_RELS)) -_ := $(shell mkdir -p $(BUILD); echo "$(KNOWN_RELS)" > $(MODLIST); rm -f $(LIB)) +_ := $(shell mkdir -p $(BUILD); echo "$(KNOWN_RELS)" > $(MODLIST); rm -f $(LIB_FAST) $(LIB_SAFE)) endif -LIB_DIR := $(PROJ_ROOT)/lib -LIB := $(LIB_DIR)/sprinter.lib +LIB_DIR := $(PROJ_ROOT)/lib +LIB_FAST := $(LIB_DIR)/sprinter.lib +LIB_SAFE := $(LIB_DIR)/sprinter_safe.lib -all: $(LIB) +all: fast safe -# Pattern rule для C-модулей libc//file.c → build//file.rel -# (сохраняет путь io|mem|stdio внутри архива). -$(BUILD)/%.rel: $(CURDIR)/%.c +fast: $(LIB_FAST) + +safe: $(LIB_SAFE) + +# --- Fast: -DLIBC_NOCHECK (параметр-валидации выключены) ------------ +$(BUILD)/fast/%.rel: $(CURDIR)/%.c + @mkdir -p $(dir $@) + $(SDCC) $(CC_FLAGS) -DLIBC_NOCHECK -c -o $@ $< + +$(BUILD)/fast/%.rel: $(PROJ_ROOT)/runtime/%.s + @mkdir -p $(dir $@) + $(SDASZ80) -o $@ $< + +# --- Safe: валидации включены ---------------------------------------- +$(BUILD)/safe/%.rel: $(CURDIR)/%.c @mkdir -p $(dir $@) $(SDCC) $(CC_FLAGS) -c -o $@ $< -# Runtime .s → .rel (runtime/ вне libc/, но heap.s бандлится в архив). -$(BUILD)/%.rel: $(PROJ_ROOT)/runtime/%.s +$(BUILD)/safe/%.rel: $(PROJ_ROOT)/runtime/%.s @mkdir -p $(dir $@) $(SDASZ80) -o $@ $< # Archive — sdar rcs = replace/create/symtab. Перед упаковкой удаляем # stale .rel: wildcard не знает об удалённых/переименованных исходниках, # и осиротевший .rel из build/ иначе жил бы вечно. -$(LIB): $(ALL_RELS) $(MODLIST) - @find $(BUILD) -name '*.rel' -print | sort > $(BUILD)/.have - @printf '%s\n' $(KNOWN_RELS) | sort > $(BUILD)/.want - @comm -23 $(BUILD)/.have $(BUILD)/.want | while read f; do \ +define PACK_LIB + @find $(BUILD)/$(1) -name '*.rel' -print | sort > $(BUILD)/.have_$(1) + @printf '%s\n' $(2) | sort > $(BUILD)/.want_$(1) + @comm -23 $(BUILD)/.have_$(1) $(BUILD)/.want_$(1) | while read f; do \ echo " stale: $$f (исходник удалён) — rm"; \ rm -f "$$f" "$${f%.rel}.asm" "$${f%.rel}.lst" "$${f%.rel}.sym"; \ done rm -f $@ - $(SDAR) -rcs $@ $(ALL_RELS) - @echo - @echo " Built $@ with $(words $(ALL_RELS)) modules:" - @$(SDAR) -t $@ | sed 's/^/ /' + $(SDAR) -rcs $@ $(2) + @echo " Built $@ with $(words $(2)) modules." +endef + +$(LIB_FAST): $(FAST_RELS) $(MODLIST) + $(call PACK_LIB,fast,$(FAST_RELS)) + +$(LIB_SAFE): $(SAFE_RELS) $(MODLIST) + $(call PACK_LIB,safe,$(SAFE_RELS)) clean: - rm -rf $(BUILD) $(LIB) + rm -rf $(BUILD) $(LIB_FAST) $(LIB_SAFE) -.PHONY: all clean +.PHONY: all fast safe clean diff --git a/libc/conio/cputs.c b/libc/conio/cputs.c index 0900968..b62b364 100644 --- a/libc/conio/cputs.c +++ b/libc/conio/cputs.c @@ -19,10 +19,12 @@ char cputs(const char *s) __naked __asm ;; HL = s на входе; возврат char → A. +#ifndef LIBC_NOCHECK ;; NULL-check: cputs(NULL) → сразу вернуть 0. ld a, h or a, l ret z +#endif push ix diff --git a/libc/file/fclose.c b/libc/file/fclose.c index b1b901f..2a212d0 100644 --- a/libc/file/fclose.c +++ b/libc/file/fclose.c @@ -13,10 +13,12 @@ int fclose(FILE *fp) { +#ifndef LIBC_NOCHECK if (!fp) { errno = EBADF; return EOF; } +#endif if (fp->flags & (_F_CONIN | _F_CONOUT)) { return 0; } diff --git a/libc/file/fgetc.c b/libc/file/fgetc.c index 149a389..2d419e5 100644 --- a/libc/file/fgetc.c +++ b/libc/file/fgetc.c @@ -22,7 +22,9 @@ * пределах этого модуля). */ static int fgetc_slow(FILE *fp) { +#ifndef LIBC_NOCHECK if (!fp) { errno = EBADF; return EOF; } +#endif if (fp->hold != EOF) { int c = fp->hold; fp->hold = EOF; @@ -62,9 +64,11 @@ int fgetc(FILE *fp) __naked (void)fgetc_slow; /* используется из asm */ __asm ;; HL = fp. +#ifndef LIBC_NOCHECK ld a, h or a, l jr Z, _fg_slow ; NULL — C-путь выставит errno +#endif push hl pop iy ; IY = fp (IY — scratch в __sdcccall(1)) diff --git a/libc/file/fgets.c b/libc/file/fgets.c index 838ca5b..1ca8a83 100644 --- a/libc/file/fgets.c +++ b/libc/file/fgets.c @@ -51,7 +51,9 @@ static uint16_t fgets_scan(uint8_t *dst, const uint8_t *src, char *fgets(char *buf, int n, FILE *fp) { +#ifndef LIBC_NOCHECK if (!buf || n < 1 || !fp) return NULL; +#endif uint8_t *dst = (uint8_t *)buf; int space = n - 1; diff --git a/libc/file/fputc.c b/libc/file/fputc.c index 88ae042..639c2a5 100644 --- a/libc/file/fputc.c +++ b/libc/file/fputc.c @@ -25,7 +25,9 @@ /* Медленный путь; зовётся из asm-обёртки. */ static int fputc_slow(int c, FILE *fp) { +#ifndef LIBC_NOCHECK if (!fp) { errno = EBADF; return EOF; } +#endif if (fp->flags & _F_CONOUT) { putchar(c); return (int)(uint8_t)c; @@ -63,9 +65,11 @@ int fputc(int c, FILE *fp) __naked (void)fputc_slow; /* используется из asm */ __asm ;; HL = c, DE = fp. +#ifndef LIBC_NOCHECK ld a, d or a, e jr Z, _fp_slow ; NULL — C-путь выставит errno +#endif push de pop iy ; IY = fp diff --git a/libc/file/fputs.c b/libc/file/fputs.c index a22e028..77c68ec 100644 --- a/libc/file/fputs.c +++ b/libc/file/fputs.c @@ -10,7 +10,9 @@ int fputs(const char *s, FILE *fp) { +#ifndef LIBC_NOCHECK if (!fp || !s) return EOF; +#endif size_t n = strlen(s); if (n == 0) return 0; return fwrite(s, 1, n, fp) == n ? 0 : EOF; diff --git a/libc/file/fread.c b/libc/file/fread.c index 21431e5..346d4b7 100644 --- a/libc/file/fread.c +++ b/libc/file/fread.c @@ -16,7 +16,9 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) { +#ifndef LIBC_NOCHECK if (!ptr || !fp) return 0; +#endif if (size == 0 || nmemb == 0) return 0; uint8_t *p = (uint8_t *)ptr; @@ -39,7 +41,9 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) } goto done; } +#ifndef LIBC_NOCHECK if (!(fp->flags & _F_READ)) { errno = EBADF; return 0; } +#endif if (fp->flags & _F_DIROUT) { if (_file_sync(fp)) return 0; /* сброс накопленной записи */ } diff --git a/libc/file/fseek.c b/libc/file/fseek.c index 5258c99..75beaf3 100644 --- a/libc/file/fseek.c +++ b/libc/file/fseek.c @@ -14,7 +14,10 @@ int fseek(FILE *fp, long off, int whence) { - if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) { +#ifndef LIBC_NOCHECK + if (!fp) { errno = EBADF; return -1; } +#endif + if (fp->flags & (_F_CONIN | _F_CONOUT)) { errno = EBADF; return -1; } diff --git a/libc/file/ftell.c b/libc/file/ftell.c index 25a1f33..50d5693 100644 --- a/libc/file/ftell.c +++ b/libc/file/ftell.c @@ -12,7 +12,10 @@ long ftell(FILE *fp) { - if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) { +#ifndef LIBC_NOCHECK + if (!fp) { errno = EBADF; return -1L; } +#endif + if (fp->flags & (_F_CONIN | _F_CONOUT)) { errno = EBADF; return -1L; } diff --git a/libc/file/fwrite.c b/libc/file/fwrite.c index b0133cc..a341b6d 100644 --- a/libc/file/fwrite.c +++ b/libc/file/fwrite.c @@ -18,7 +18,9 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp) { +#ifndef LIBC_NOCHECK if (!ptr || !fp) return 0; +#endif if (size == 0 || nmemb == 0) return 0; const uint8_t *p = (const uint8_t *)ptr; @@ -30,7 +32,9 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp) } return nmemb; } +#ifndef LIBC_NOCHECK if (!(fp->flags & _F_WRITE)) { errno = EBADF; return 0; } +#endif if (!(fp->flags & _F_DIROUT)) { if (_file_sync(fp)) return 0; /* откат readahead/hold */ diff --git a/libc/file/ungetc.c b/libc/file/ungetc.c index 20c5c4f..a1e8a1a 100644 --- a/libc/file/ungetc.c +++ b/libc/file/ungetc.c @@ -10,7 +10,10 @@ int ungetc(int c, FILE *fp) { - if (!fp || c == EOF) return EOF; +#ifndef LIBC_NOCHECK + if (!fp) return EOF; +#endif + if (c == EOF) return EOF; /* семантика стандарта */ if (fp->hold != EOF) return EOF; /* место занято */ if (fp->flags & _F_CONOUT) return EOF; if (!(fp->flags & (_F_READ | _F_CONIN))) return EOF;