48d552bf3a
- единый ленивый буфер BUFSIZ=512 на чтение и запись с автопереключением направления (_F_DIROUT, _file_sync: запись сбрасывается write()-ом, readahead откатывается lseek-ом) - статическая таблица OPEN_MAX=8 слотов вместо malloc для FILE; _fclosall через atexit — exit() сбрасывает несброшенную запись - fread/fwrite: мелкое через буфер (memcpy), блоки >= BUFSIZ — мимо буфера одним syscall; горячие пути fgetc/fputc и сканер строк fgets (LDI до '\n') — на asm, SDCC на эти цепочки генерит ~90 инструкций с IX-фреймом - новое: ungetc (1 байт через hold, работает и на stdin), fprintf/vfprintf (vsprintf+fwrite), fflush(NULL) = все потоки - фиксы stdio-review: fwrite ставит _F_ERROR при короткой записи (issue 3), fgets(n=1) возвращает пустую строку (issue 4) - замер (MAME, HDD, 100 КБ): небуферизованная оценка ~144 с → fgetc 5 с (×29), fgets ~1 с; дизайн и отвергнутые варианты — docs/file-buffering-design.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
3.7 KiB
C
102 lines
3.7 KiB
C
/*
|
||
* fgetc — прочитать один байт из потока (буферизованно, вариант B+).
|
||
*
|
||
* Горячий путь («в readahead-буфере есть байт») написан на asm —
|
||
* SDCC на эту цепочку проверок генерит ~90 инструкций с IX-фреймом,
|
||
* asm укладывается в ~30 без фрейма (замер fbench: ~176 мкс/байт
|
||
* у C-версии). Всё остальное — медленный C-путь fgetc_slow:
|
||
* hold-байт от ungetc, консольный getchar(), сброс накопленной
|
||
* записи, дозаполнение буфера read(fd, buf, BUFSIZ), fallback без
|
||
* буфера. Возвращает байт, EOF на конце файла (_F_EOF) или при
|
||
* ошибке (_F_ERROR).
|
||
*
|
||
* Раскладка FILE (см. stdio.h, байтовые смещения):
|
||
* +0 fd, +2 flags, +3 buf, +5 curp, +7 level, +9 hold
|
||
*/
|
||
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include "_file.h"
|
||
|
||
/* Медленный путь; зовётся из asm-обёртки (метка _fgetc_slow видна в
|
||
* пределах этого модуля). */
|
||
static int fgetc_slow(FILE *fp)
|
||
{
|
||
if (!fp) { errno = EBADF; return EOF; }
|
||
if (fp->hold != EOF) {
|
||
int c = fp->hold;
|
||
fp->hold = EOF;
|
||
return c;
|
||
}
|
||
if (fp->flags & _F_CONIN) {
|
||
return getchar();
|
||
}
|
||
if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; }
|
||
|
||
if (fp->flags & _F_DIROUT) {
|
||
if (_file_sync(fp)) return EOF; /* сброс накопленной записи */
|
||
}
|
||
if (fp->level == 0) {
|
||
uint8_t *b = _file_buf(fp);
|
||
if (!b) {
|
||
/* Небуферизованный fallback. */
|
||
uint8_t ch;
|
||
int r = read(fp->fd, &ch, 1);
|
||
if (r == 0) { fp->flags |= _F_EOF; return EOF; }
|
||
if (r < 0) { fp->flags |= _F_ERROR; return EOF; }
|
||
return (int)ch;
|
||
}
|
||
int r = read(fp->fd, b, BUFSIZ);
|
||
if (r == 0) { fp->flags |= _F_EOF; return EOF; }
|
||
if (r < 0) { fp->flags |= _F_ERROR; return EOF; }
|
||
fp->level = (uint16_t)r;
|
||
fp->curp = b;
|
||
}
|
||
fp->level--;
|
||
return (int)*fp->curp++;
|
||
}
|
||
|
||
int fgetc(FILE *fp) __naked
|
||
{
|
||
(void)fp;
|
||
(void)fgetc_slow; /* используется из asm */
|
||
__asm
|
||
;; HL = fp.
|
||
ld a, h
|
||
or a, l
|
||
jr Z, _fg_slow ; NULL — C-путь выставит errno
|
||
push hl
|
||
pop iy ; IY = fp (IY — scratch в __sdcccall(1))
|
||
|
||
ld a, 7 (iy) ; level != 0 ?
|
||
or a, 8 (iy)
|
||
jr Z, _fg_slow ; буфер пуст/не наш режим
|
||
ld a, 2 (iy) ; flags
|
||
and a, #0x08 ; _F_DIROUT — буфер копит запись?
|
||
jr NZ, _fg_slow
|
||
ld a, 9 (iy) ; hold == EOF (0xFFFF)?
|
||
and a, 10 (iy)
|
||
inc a
|
||
jr NZ, _fg_slow ; есть отложенный ungetc-байт
|
||
|
||
;; --- горячий путь: level--, вернуть *curp++ ---
|
||
ld a, 7 (iy)
|
||
sub a, #1
|
||
ld 7 (iy), a
|
||
jr NC, _fg_nb
|
||
dec 8 (iy)
|
||
_fg_nb:
|
||
ld l, 5 (iy) ; HL = curp
|
||
ld h, 6 (iy)
|
||
ld e, (hl) ; E = байт
|
||
ld d, #0 ; zero-extend в int
|
||
inc hl
|
||
ld 5 (iy), l ; curp++
|
||
ld 6 (iy), h
|
||
ret
|
||
|
||
_fg_slow:
|
||
jp _fgetc_slow ; HL = fp уже на месте
|
||
__endasm;
|
||
}
|