boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 14855baae25c60c5747ae85815079ad84d16e992
parent a7ada8d0d20a88c00a35a489c30c309c20a0b815
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 27 Apr 2026 18:58:06 -0700

cc: float/double parse

Diffstat:
Mcc/cc.scm | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdocs/CC.md | 14++++++++++----
Atests/cc/119-float-parse.c | 26++++++++++++++++++++++++++
Atests/cc/119-float-parse.expected-exit | 1+
4 files changed, 94 insertions(+), 10 deletions(-)

diff --git a/cc/cc.scm b/cc/cc.scm @@ -356,6 +356,12 @@ (define %t-i64 (%ctype 'i64 8 8 #f)) (define %t-u64 (%ctype 'u64 8 8 #f)) (define %t-bool (%ctype 'bool 1 1 #f)) +;; Floating-point ctypes are parsed but never codegen'd; see CC.md §Cut. +;; Sizes/aligns match the SysV ABI so struct layout containing fp fields +;; works even when the cg refuses to emit fp ops. +(define %t-flt (%ctype 'flt 4 4 #f)) +(define %t-dbl (%ctype 'dbl 8 8 #f)) +(define %t-ldbl (%ctype 'ldbl 8 8 #f)) ;; -------------------------------------------------------------------- ;; sym — declared identifier (function, variable, typedef, …) @@ -2349,6 +2355,7 @@ (%n shift-amount) ")\n"))) (define (%cg-emit-ld-slot-typed cg reg ctype logical-off) + (%cg-fp-reject! 'ld-slot ctype) (let* ((sz (ctype-size ctype)) (kind (ctype-kind ctype)) (off-fn (lambda (k) (%cg-slot-expr cg (+ logical-off k))))) (cond @@ -2365,6 +2372,7 @@ (else (%cg-emit-ld-slot cg reg logical-off))))) (define (%cg-emit-st-slot-typed cg reg ctype logical-off) + (%cg-fp-reject! 'st-slot ctype) (let* ((sz (ctype-size ctype)) (off-fn (lambda (k) (%cg-slot-expr cg (+ logical-off k))))) (cond @@ -2376,6 +2384,7 @@ (else (%cg-emit-st-slot cg reg logical-off))))) (define (%cg-emit-ld-typed cg reg ctype base off) + (%cg-fp-reject! 'ld ctype) (let* ((sz (ctype-size ctype)) (kind (ctype-kind ctype)) (base-bv (%cg-reg->bv base)) (off-fn (lambda (k) (%n (+ off k))))) @@ -2393,6 +2402,7 @@ (else (%cg-emit-ld cg reg base off))))) (define (%cg-emit-st-typed cg reg ctype base off) + (%cg-fp-reject! 'st ctype) (let* ((sz (ctype-size ctype)) (base-bv (%cg-reg->bv base)) (off-fn (lambda (k) (%n (+ off k))))) @@ -2410,6 +2420,7 @@ ;; global lval width > 1 byte-gathers must not alias dest with base — ;; the first %lb would otherwise clobber the address before subsequent ;; byte loads. Stage the address in t2. + (%cg-fp-reject! 'load (opnd-type op)) (pmatch op (($ opnd? (kind imm) (ext ,n)) (%cg-emit-li cg reg n)) (($ opnd? (kind frame) (lval? #t) (type ,ty) (ext ,off)) @@ -2444,6 +2455,19 @@ ((eq? k 'ptr) #t) ((eq? k 'arr) #t) ((eq? k 'fn) #t) (else #f)))) +(define (%ctype-fp? t) + (let ((k (ctype-kind t))) + (cond ((eq? k 'flt) #t) ((eq? k 'dbl) #t) ((eq? k 'ldbl) #t) + (else #f)))) + +;; Floating-point ops are parsed but the cg refuses to emit anything that +;; would touch fp bits. Any ld/st/cast/load involving an fp ctype trips +;; this. Caught up front in tcc.flat.c via mes-header prototypes only; +;; if a real fp use leaks through we want a crisp diagnostic, not silent +;; integer codegen. +(define (%cg-fp-reject! op-name ty) + (if (%ctype-fp? ty) (die #f "fp not codegen'd" op-name (ctype-kind ty)))) + (define (%ctype-size t) (ctype-size t)) (define (%reg-by-idx i) @@ -2978,6 +3002,8 @@ (from-sz (%ctype-size from-ty)) (to-sz (%ctype-size to-type)) (to-kind (ctype-kind to-type))) + (%cg-fp-reject! 'cast-to to-type) + (%cg-fp-reject! 'cast-from from-ty) (cond ((eq? to-kind 'bool) (%cg-load-opnd-into cg p 't0) @@ -3713,9 +3739,15 @@ ((at-kw? ps 'char) (advance ps) (loop sto sn lg 'char #t)) ((at-kw? ps 'int) (advance ps) (loop sto sn lg 'int #t)) ((at-kw? ps '_Bool) (advance ps) (loop sto sn lg 'bool #t)) - ((or (at-kw? ps 'float) (at-kw? ps 'double) - (at-kw? ps '_Complex) (at-kw? ps '_Imaginary)) - (die (tok-loc t) "no float" (tok-value t))) + ;; Floats: parsed as type specifiers so prototypes and struct + ;; layouts in the flattened TU don't trip the parser. The cg + ;; rejects fp loads/arith/casts at use, see %cg-fp-reject!. + ;; _Complex / _Imaginary are eaten silently — tcc.c only mentions + ;; them inside HAVE_FLOAT-gated paths. + ((at-kw? ps 'float) (advance ps) (loop sto sn lg 'float #t)) + ((at-kw? ps 'double) (advance ps) (loop sto sn lg 'double #t)) + ((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)) @@ -3749,6 +3781,13 @@ (cond ((= lg -1) (if (eq? sn 'unsigned) %t-u16 %t-i16)) ((= lg 0) (if (eq? sn 'unsigned) %t-u32 %t-i32)) (else (if (eq? sn 'unsigned) %t-u64 %t-i64)))) + ((eq? b 'float) + (if (or sn (not (zero? lg))) (die loc "float+qual") %t-flt)) + ((eq? b 'double) + (cond (sn (die loc "double+sign")) + ((= lg 0) %t-dbl) + ((= lg 1) %t-ldbl) + (else (die loc "double+long*" lg)))) ((ctype? b) (if (or sn (not (zero? lg))) (die loc "type+qual") b)) (else (die loc "unknown decl-spec")))) @@ -4114,6 +4153,8 @@ (($ tok? (kind KW) (value ,v)) (or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) (eq? v '_Bool) + (eq? v 'float) (eq? v 'double) + (eq? v '_Complex) (eq? v '_Imaginary) (eq? v 'struct) (eq? v 'union) (eq? v 'enum) (eq? v 'const) (eq? v 'volatile) (eq? v 'restrict))) (($ tok? (kind IDENT) (value ,n)) (typedef? ps n)) @@ -4166,7 +4207,9 @@ (($ tok? (kind KW) (value ,v)) (or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) - (eq? v '_Bool) (eq? v 'struct) (eq? v 'union) + (eq? v '_Bool) (eq? v 'float) (eq? v 'double) + (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))) (($ tok? (kind IDENT) (value ,n)) (typedef? ps n)) @@ -4254,6 +4297,8 @@ (cond ((or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) (eq? v '_Bool) + (eq? v 'float) (eq? v 'double) + (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 'static) @@ -5000,7 +5045,9 @@ (eq? v 'volatile) (eq? v 'restrict) (eq? v 'inline) (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) - (eq? v '_Bool) (eq? v 'struct) (eq? v 'union) + (eq? v '_Bool) (eq? v 'float) (eq? v 'double) + (eq? v '_Complex) (eq? v '_Imaginary) + (eq? v 'struct) (eq? v 'union) (eq? v 'enum))) (($ tok? (kind IDENT) (value ,n)) (typedef? ps n)) (else #f))) @@ -5411,7 +5458,9 @@ (($ tok? (kind KW) (value ,v)) (or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) - (eq? v '_Bool) (eq? v 'struct) (eq? v 'union) + (eq? v '_Bool) (eq? v 'float) (eq? v 'double) + (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))) (($ tok? (kind IDENT) (value ,n)) (typedef? ps n)) @@ -5423,6 +5472,8 @@ (guard (or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int) (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned) (eq? v '_Bool) + (eq? v 'float) (eq? v 'double) + (eq? v '_Complex) (eq? v '_Imaginary) (eq? v 'struct) (eq? v 'union) (eq? v 'enum) (eq? v 'const) (eq? v 'volatile) (eq? v 'restrict))) diff --git a/docs/CC.md b/docs/CC.md @@ -184,8 +184,14 @@ defense against `#define A B\n#define B A`. `uintptr_t` are `long` / `unsigned long`. These typedefs come from the flattened headers; the language doesn't bake them in. -**Not present**: `float`, `double`, `long double`, `_Complex`, -`_Imaginary`, `__int128`. `float.h` macros and `<math.h>` are +**Floating-point types** (`float`, `double`, `long double`, +`_Complex`, `_Imaginary`) are **parsed but never codegen'd**: prototypes +and struct fields involving them are accepted (so the flattened tcc.c +TU can be ingested), and `sizeof` reports the standard SysV widths +(4/8/8). Any attempt to materialize an fp value — load, store, cast, +arithmetic, call/return — dies with `fp not codegen'd`. tcc.c only uses +fp under `HAVE_FLOAT`, which is off, so live code never trips the cg +guard. **Not present**: `__int128`. `float.h` macros and `<math.h>` are unavailable to the input. ### Derived types @@ -394,8 +400,8 @@ Kept explicit so additions are deliberate. | Feature | Status | Rationale | |-----------------------------------------------|----------|------------------------------------------------| -| Floats / doubles / `_Complex` | rejected | HAVE_FLOAT off | -| `long double` | rejected | no FP | +| Floats / doubles / `_Complex` | parse-only | parsed as types; cg rejects fp ops (HAVE_FLOAT off) | +| `long double` | parse-only | same softening; sized as 8 bytes | | Bitfields | rejected | HAVE_BITFIELD off | | `setjmp` / `longjmp` | not lib | HAVE_SETJMP off | | VLAs | rejected | tcc.c doesn't use; complicates frame layout | diff --git a/tests/cc/119-float-parse.c b/tests/cc/119-float-parse.c @@ -0,0 +1,26 @@ +/* Float/double type specifiers are parsed without error. + * The cg never materializes fp values; this exercises only the + * declarations + struct layout via sizeof. See docs/CC.md §Cut + * and docs/TCC-TODO.md blocker 1. */ + +extern double atof(const char *); +extern float strtof(const char *, char **); +extern long double strtold(const char *, char **); +extern int ieee_finite(double d); + +struct fields { + int tag; + float f; + double d; + long double ld; +}; + +int main(int argc, char **argv) { + /* sizeof works without loading fp bits. */ + if (sizeof(float) != 4) return 1; + if (sizeof(double) != 8) return 2; + if (sizeof(long double) != 8) return 3; + /* struct layout: tag(4) + f(4) + d(8) + ld(8) = 24. */ + if (sizeof(struct fields) != 24) return 4; + return 0; +} diff --git a/tests/cc/119-float-parse.expected-exit b/tests/cc/119-float-parse.expected-exit @@ -0,0 +1 @@ +0