commit 14855baae25c60c5747ae85815079ad84d16e992
parent a7ada8d0d20a88c00a35a489c30c309c20a0b815
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 27 Apr 2026 18:58:06 -0700
cc: float/double parse
Diffstat:
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