9f8aa6fc28
Симметрично libbgi (bgi256/bgi256_safe): fast = -DLIBC_NOCHECK, дефолт sprinter-cc; safe линкуется по --safe (флаг уже существовал). Под LIBC_NOCHECK вырезаны ТОЛЬКО параметр-валидации: - fgetc/fputc: NULL-check в горячей asm-обёртке (~11Т на каждый байт); - fgets/fputs/fread/fwrite: NULL ptr/fp и EBADF на неверное направление потока; ftell/fseek/ungetc/fclose: NULL fp; cputs: NULL s. НЕ тронуты: критичный _fd_guard (9-й OPEN вешает DSS — в обеих версиях), функциональная маршрутизация (консоль/направление/hold), cold-path валидации (fopen/cbl_open/irq/settextmode — экономии ноль). libc/Makefile — dual-build по образцу libbgi (build/fast + build/safe, общий стейл-контроль). Корневой Makefile: в TESTS добавлены bgitest, bgi_img, accfill, cblstream — раньше не собирались корневым make и выпадали из size-check при чистой пересборке. Дельты fast vs старая (safe-семантика): filetest -481, fbench -384, solidt -180, errno -71, остальные -3..-9; роста нет. Проверено в MAME: filetest fast и safe (--safe, sprinter_safe.lib) — прогоны идентичны. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.6 KiB
C
87 lines
3.6 KiB
C
/*
|
||
* fgets — прочитать строку (до n-1 символов, '\n' сохраняется) из
|
||
* потока в buf с завершающим NUL. Возвращает buf или NULL, если EOF
|
||
* наступил до первого сохранённого символа. n=1 по стандарту —
|
||
* валидный вызов: ничего не читает, возвращает пустую строку.
|
||
*
|
||
* Быстрый путь: пока строка лежит в readahead-буфере, она копируется
|
||
* блоком asm-сканером (LDI + сравнение с '\n', ~50 тактов/байт) —
|
||
* fgetc-на-символ был в ~10 раз медленнее (замер fbench). Медленные
|
||
* случаи (консоль, hold от ungetc, режим записи, дозаполнение буфера,
|
||
* EOF) уходят посимвольно через fgetc.
|
||
*/
|
||
|
||
#include "_file.h"
|
||
|
||
/* Копировать из src в dst до max байт, остановившись ПОСЛЕ '\n'.
|
||
* Возврат: число скопированных байт | 0x8000, если '\n' найден.
|
||
* max: 1..512 (бит 15 счётчика свободен). */
|
||
static uint16_t fgets_scan(uint8_t *dst, const uint8_t *src,
|
||
uint16_t max) __naked
|
||
{
|
||
(void)dst; (void)src; (void)max;
|
||
__asm
|
||
;; HL = dst, DE = src, max на стеке (callee чистит).
|
||
pop iy ; адрес возврата
|
||
pop bc ; BC = max (> 0)
|
||
ex de, hl ; HL = src, DE = dst
|
||
push bc ; max понадобится для подсчёта
|
||
_fgs_loop:
|
||
ld a, (hl)
|
||
cp a, #0x0A ; сравнить ДО ldi: LDI не трогает Z,
|
||
ldi ; но выставляет P/V по BC ((DE)<-(HL))
|
||
jr Z, _fgs_found ; Z — от cp
|
||
jp PE, _fgs_loop ; P/V — от LDI: BC != 0
|
||
;; max исчерпан, '\n' нет: count = max
|
||
pop de
|
||
jp (iy)
|
||
_fgs_found:
|
||
;; count = max - BC, бит 15 = флаг '\n' (count <= 512 — безопасно)
|
||
pop hl ; HL = max
|
||
ld a, l
|
||
sub a, c
|
||
ld e, a
|
||
ld a, h
|
||
sbc a, b
|
||
or a, #0x80
|
||
ld d, a
|
||
jp (iy)
|
||
__endasm;
|
||
}
|
||
|
||
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;
|
||
|
||
while (space > 0) {
|
||
if (fp->level && !(fp->flags & _F_DIROUT) && fp->hold == EOF) {
|
||
/* Быстрый блок: сканировать readahead до '\n'. */
|
||
uint16_t take = fp->level;
|
||
if (take > (uint16_t)space) take = (uint16_t)space;
|
||
uint16_t got = fgets_scan(dst, fp->curp, take);
|
||
uint16_t cnt = got & 0x7FFF;
|
||
fp->curp += cnt;
|
||
fp->level -= cnt;
|
||
dst += cnt;
|
||
space -= (int)cnt;
|
||
if (got & 0x8000) break; /* строка закончена */
|
||
continue; /* буфер иссяк — дозаполнить */
|
||
}
|
||
/* Медленные случаи + refill/EOF — посимвольно. */
|
||
int c = fgetc(fp);
|
||
if (c == EOF) {
|
||
if (dst == (uint8_t *)buf) return NULL;
|
||
break;
|
||
}
|
||
*dst++ = (uint8_t)c;
|
||
space--;
|
||
if (c == '\n') break;
|
||
}
|
||
*dst = '\0';
|
||
return buf;
|
||
}
|