From 60222935347fde89fad4ba8605c383b3fcf41c6c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 02:55:36 +0000 Subject: [PATCH] cfront: -J makes kencc agree with the translator on all seven 14 of 97 -> 7 with pcc -J. Every type cfront owns falls into line: node 3, name 144, basic_inst 168, funct_inst 200, state 88, templ_compilation 1, templ_inst 200. The seven left are the glibc types, which -J cannot touch because they describe the machine that generated the C, and the two __Q3_ entries confirm the reading rather than vanishing: _C4 is 20 with kencc's 32-bit __clock_t against Linux's 32, _C6 is 8 against 16. The two instruments never disagreed. structalign-test was compiled with -J and cfrontsz-probe without it, and I read them as the same compile for two rounds. apeabi-probe.c is the instrument for the question that comes next. It is never run: compiled twice, pcc -a against pcc -J -a, and the diff of the two acid dumps names every APE struct whose layout -J moves, from the compiler itself, before a single object is rebuilt. 0 errors against the staged headers on the host. And it is needed because the obvious way to turn -J on does not work. In sys/src/ape/config's CFLAGS it reaches 32 of 137 mkfiles: the other 105 assign CFLAGS= rather than appending $CFLAGS, and cmd/cfront/mkfile:52 is one of them. CC is no better, 59 mkfiles reassign it. Either would have produced the silent ABI split rather than a clean change. The only mechanism that reaches every APE compile is pcc itself, since all 59 reassignments still name pcc, and native 6c builds then keep the 9front rule that cmd2/vts and vtwin need against the host's libc.a. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs --- CLAUDE.md | 54 +++++++++++++ sys/lib/tests/apeabi-probe.c | 143 +++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 sys/lib/tests/apeabi-probe.c diff --git a/CLAUDE.md b/CLAUDE.md index 943024e01..19b6d37f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -303,6 +303,10 @@ glibc's take `const char *`, so even a `-D` rename collides with the header -- it renames the SOURCE with `sed` into a temporary instead. `strtol`/`strtoul` cannot be swept at all here, because kencc's `long` is 32-bit and the host's is 64. +`apeabi-probe.c` is a PROBE and is never run: it is compiled TWICE, +`pcc -a` and `pcc -J -a`, and the DIFF of the two acid dumps names +every APE struct whose layout `-J` moves. Take it before any +tree-wide rebuild. `structalign-test.c` measures `pcc -J`, conforming struct layout, and **needs three runs** -- gcc, then `pcc -DPLAN9` WITHOUT `-J` (which must fail ~12 checks), then with it. *The middle run is the control*: @@ -929,6 +933,56 @@ one run tells the two instruments apart rather than two rounds. *The predicted failure did not fire and the prediction was written down; that is the condition doing its job, not the flag.* +**AND `-J` WAS WORKING THE WHOLE TIME -- 14 of 97 -> 7, EXACTLY THE +PREDICTION.** All seven of cfront's OWN types fall into line under +`pcc -J`: `node` 3, **`name` 144**, `basic_inst` 168, `funct_inst` +200, `state` 88, `templ_compilation` 1, `templ_inst` 200. What is +left is the seven glibc types, which `-J` cannot touch because they +describe the machine that GENERATED the C -- and the two `__Q3_` +entries confirm the reading rather than merely vanishing: `_C4` is +**20** (`__clock_t` four times over at 4 bytes) against Linux's 32, +`_C6` is 8 (`long si_band` + `int si_fd`) against 16. *The 32-bit +`long` invariant showing through, not padding.* +**THE "FLAG NOT PICKED UP" MESSAGE WAS THE MARKER, NOT THE FLAG, AND +IT COST THREE REBUILDS.** `pcc` runs **`/bin/cpp` ITSELF** and pipes +it into `cc` (`pcc.c:299`'s `dopipe`), so the source reaches `cc` +**already preprocessed** -- and neither `dodefine()` in `cc`'s own +symbol table nor `cc`'s `defs[]` can define a macro the source will +ever see. *There are THREE preprocessors in play and only the one +`pcc` spawns actually runs*; the first two attempts configured the +other two. **Name the preprocessor that RUNS, not the one with the +right name** -- the shadowed-`config.h` lesson a third time. +**And the two instruments never disagreed.** `structalign-test` was +compiled **with** `-J` and `cfrontsz-probe` **without** it, and I +read them as the same compile for two rounds. Everything else was +cleared locally first: preprocessing the probe's own translation unit +with cfront's exact flags gives ONE `struct node`, `TOK` and `bit` +both `unsigned char`, and gcc computing 3; `cfront_translated.h` +defines none of the seven; `-B` touches only undeclared-function +diagnostics. *Two compiles differing in one flag is not two compilers +disagreeing, and the flag was in the command line both times.* + +**THE OBVIOUS WAY TO TURN `-J` ON TREE-WIDE DOES NOT WORK, and that +is the finding to carry.** `-J` in `sys/src/ape/config`'s CFLAGS +reaches **32 of 137** mkfiles -- the other **105 ASSIGN `CFLAGS=`** +rather than appending `$CFLAGS`, and `cmd/cfront/mkfile:52` is one of +them. `CC` is no better: **59** mkfiles reassign it. *Both of the two +variables a build system offers for exactly this have holes, and +either would have produced the silent ABI split rather than a clean +change* -- 32 packages conforming, 105 not, every symbol still +resolving. **The only mechanism that reaches every APE compile is +`pcc` itself**, since all 59 reassignments still name `pcc`; native +`6c` builds stay on the 9front rule, which is what `cmd2/vts` and +`vtwin` need when they link the host's own `libc.a`. +**`apeabi-probe.c` is what to run BEFORE deciding that**: two +compiles and a diff, `pcc -a` against `pcc -J -a`. `-a` emits acid +definitions carrying every struct's size and every member's offset +(it is `mkone`'s own `%.acid` rule), so the diff is a complete list, +**from the compiler itself**, of everything `-J` moves inside the APE +world. An empty diff would mean the flag is free; a long one is the +cost named struct by struct before a single object is rebuilt. 0 +errors against the staged headers on the host. + **AND THE cfront SIZE PROBE WAS `.gitignore'd, SO THE VM NEVER GOT IT.** `pcc ... cfrontsz-probe.c` answered `Can't open input file` and `ls cfront*.c` showed only the two stub files. **git is the only diff --git a/sys/lib/tests/apeabi-probe.c b/sys/lib/tests/apeabi-probe.c new file mode 100644 index 000000000..708453dc9 --- /dev/null +++ b/sys/lib/tests/apeabi-probe.c @@ -0,0 +1,143 @@ +/* + * apeabi-probe -- which APE structs does `-J' MOVE? + * + * TWO COMPILES AND A DIFF, on the VM: + * + * pcc -a apeabi-probe.c > /tmp/abi-old.acid + * pcc -J -a apeabi-probe.c > /tmp/abi-new.acid + * diff /tmp/abi-old.acid /tmp/abi-new.acid + * + * `-a' makes the compiler emit acid definitions, which carry each + * struct's SIZE and every member's OFFSET -- it is what `mkone's + * `%.acid' rule uses. So the diff is a complete list, from the + * compiler itself, of everything whose layout `-J' changes inside the + * APE world. **An empty diff would mean -J is free; a long one is the + * cost, named struct by struct, before a single object is rebuilt.** + * + * ------------------------------------------------------------------ + * WHY THIS HAS TO BE MEASURED BEFORE THE TREE-WIDE BUILD. + * + * `-J' is all-or-nothing for a linked world: it moves `FILE', + * `struct stat' and `DIR' as readily as anything else, and a package + * built with it against a libap built without it disagrees about them + * **silently, with no link error, because every symbol still + * resolves**. The failure arrives as a wrong field, later, somewhere + * else. + * + * AND THE OBVIOUS WAY TO TURN IT ON DOES NOT WORK. Putting `-J' in + * `sys/src/ape/config's CFLAGS reaches **32 of 137** mkfiles: the + * other 105 ASSIGN `CFLAGS=' outright rather than appending + * `$CFLAGS', and `cmd/cfront/mkfile:52' is one of them. Putting it on + * `CC' is no better -- 59 mkfiles reassign that too. *The two + * variables a build system offers for exactly this both have holes, + * and either one would have produced the silent split rather than a + * clean change.* + * + * So the mechanism has to be `pcc' itself, which every APE compile + * runs however the mkfile spells `$CC' -- and that is a decision to + * take with this diff in hand, not before it. + * + * ------------------------------------------------------------------ + * WHAT IS INCLUDED AND WHAT IS LEFT OUT. + * + * The ABI-bearing headers: the types that cross a library boundary, + * get embedded in another struct, or are written to disk. Headers + * that only declare functions add nothing to an acid dump. + * + * `' is here and `' is NOT, deliberately: they + * define `regex_t' and `regmatch_t' as ALTERNATIVES and no program + * includes both, which `apehdr-sweep's together case already records. + * `' and friends are out because they want `bool' as a + * keyword and this file has to compile under both compilers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The acid dump only describes types the translation unit actually + * USES, so each one is named by an object. A pointer would not do it: + * the compiler emits a definition for a struct it has laid out, and + * laying it out is what declaring an instance forces. + */ +struct stat _abi_stat; +struct dirent _abi_dirent; +struct tm _abi_tm; +struct timeval _abi_timeval; +struct timespec _abi_timespec; +struct tms _abi_tms; +struct rusage _abi_rusage; +struct utsname _abi_utsname; +struct passwd _abi_passwd; +struct group _abi_group; +struct utimbuf _abi_utimbuf; +struct sockaddr _abi_sockaddr; +struct sockaddr_in _abi_sockaddr_in; +struct sockaddr_in6 _abi_sockaddr_in6; +struct in_addr _abi_in_addr; +struct in6_addr _abi_in6_addr; +struct ip_mreq _abi_ip_mreq; +struct hostent _abi_hostent; +struct servent _abi_servent; +struct protoent _abi_protoent; +struct addrinfo _abi_addrinfo; +struct termios _abi_termios; +struct sigaction _abi_sigaction; +struct lconv _abi_lconv; +struct div_t_holder { div_t d; ldiv_t l; lldiv_t ll; }; +struct div_t_holder _abi_divs; +FILE *_abi_filep; +FILE _abi_file; +DIR *_abi_dirp; +jmp_buf _abi_jmpbuf; +sigjmp_buf _abi_sigjmpbuf; +fd_set _abi_fdset; +regex_t _abi_regex; +regmatch_t _abi_regmatch; +mbstate_t _abi_mbstate; +pthread_t _abi_pthread; +pthread_attr_t _abi_pthread_attr; +pthread_mutex_t _abi_pthread_mutex; +pthread_cond_t _abi_pthread_cond; +pthread_rwlock_t _abi_pthread_rwlock; +sigset_t _abi_sigset; + +int +main(void) +{ + /* + * Nothing is printed. The whole output is the acid dump that + * `-a' writes to stdout, so this program is never run -- only + * compiled, twice. + */ + return 0; +}