Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,18 @@ member of the wide-constant family***: x86-64 has `MOV r64, imm64` and
NO 64-bit `CMP` immediate and NO 64-bit address DISPLACEMENT.
`6c/cgen.c:346` folds `(x<<k) + C` into `LEAQ C(base)(index*2^k)` and
refused only a SMALL `C`, never a wide one. **Sections 4 and 5 are
controls that pass against a broken compiler.**).
controls that pass against a broken compiler.**),
**`boolptr-test`** (a POINTER converted to `bool`, at the four sites
that CONVERT rather than cast — return, initialiser, assignment,
argument. C99 6.5.16.1 allows it outright; `sub.c`'s `stcompat`
refused it, **and `com.c`'s `boolnorm` was already there and sat
behind the check that refused the operand** at all four. *The cast
form worked all along*, since `tcast[TUCHAR]` carries BIND.
**Sections 1 and 2 are why it is a test and not a compile**: the
plausible wrong fix keeps the LOW BYTE, so section 2 uses
`0x0000555500000000`, non-null and zero in every byte a `char`,
`short` or `int` conversion would keep — the perl `cBOOL` bug's shape
by another route).
**`truefalse-test.c` is the one NATIVE test here** — `6c`/`6l`, not
`pcc` — and has to be: APE's `<stdbool.h>` `#define`s `true`/`false`, so
a TU that INCLUDES that header never reaches kencc's own C23 keywords.
Expand Down Expand Up @@ -340,6 +351,23 @@ the compile line, `--compile` builds all 31 TUs and GATES. *Its
`long_bitsize` is patched 64 -> 32 afterwards, and leaving that out
makes `oint` 32 bits and wastes a round concluding the object
representation is broken.
**`hostkencc/php-mirror.sh` is its php counterpart and `--compile`
answers `compiled 514, errors 0`** -- the whole php build, by a
host-built `6c`, in about a minute. ***UNLIKE clisp's THERE IS NOTHING
TO STAGE***, since php's generated parsers, scanners and
`zend_vm_execute.h` all ship in the tarball -- so the file is the FLAG
SET and the source list, **both DERIVED from `cmd/php/mkfile` with
sed** (`DEFS`, `INCS`, `PCRE2FLAGS`, `KECCAKFLAGS`, `OFILES`), with a
gate if a variable comes back unexpanded. It replaced eight rounds
that each cost a VM rebuild to learn about ONE compile error; its
first run returned all eight at once. **Its own two faults are the
shapes to recognise**: it matched the Keccak flags on the DIRECTORY,
which reads like the mkfile's rule and is not it
(`ext/hash/hash_sha3.c` takes them too, from `ext/hash`), and it
passed `CFLAGS` through, whose leading **`-c` means "compile only" to
`pcc` and "print `const if`" to `6c`** -- dozens of lines that read
exactly like diagnostics. *It cannot ask about the LINK*, which stays
the VM's.
***AND `hostkencc/` BUILDS ALL TEN C COMPILERS WITH gcc***, from the
shipped `sys/src/cmd/cc` and `[1-9kqv]c` plus go1.4's `lib9`/`libbio`,
already vendored at `sys/src/external/go1.4` — which **compiled with gcc
Expand Down Expand Up @@ -1165,8 +1193,30 @@ silent:
- **itcl** — done. 10 failures, all upstream or Tcl 9's, two settled at
source, none ours.

**php — SURVEYED, NOTHING BUILT YET, and the survey is in
`docs/notes/php.md`.** 8.5.12, vendored at `sys/src/external/php`.
**php — ***ALL 514 TRANSLATION UNITS COMPILE***, measured on the HOST,
and the notes are in `docs/notes/php.md`.** 8.5.12, vendored at
`sys/src/external/php`; `sh sys/lib/tests/hostkencc/php-mirror.sh
--compile` answers `compiled 514, errors 0, missing 0`. **The LINK is
untouched and is the VM's**: the host has no libap and `hostkencc`
deliberately does not build the linkers, so the undefined-symbol class
— a `config.h` claiming a name libap only DECLARES — has not been
asked. *That question was checked by hand for the resolver family and
it held*: `res_query.c`'s `weak_alias(res_query, res_search)` is a
no-op on kencc, and `network/res_aliases.c` exists precisely to define
the strong forwarders.
**The first sweep returned EIGHT errors in six files and all six were
fixed without a VM round trip** — three of them kencc's (a pointer
assigned to a `bool`; `bool visible;` declaring nothing, because
`indeclname` had every type specifier but `LBOOL`; and the
`__attribute__`/`__asm__` paren scanner counting delimiters INSIDE
string literals, which ate php's 1680-line inlined gdb script), three
APE headers' (no BSD `timeval` macros, five missing `syslog`
facilities, no `<arpa/nameser_compat.h>`), one php's (`void *`
arithmetic) and one the `config.h`'s (`HAVE_GETHOSTBYNAME_R` yes with
every arity no). ***And the nameser fix uncovered that `<resolv.h>`
had been defining the same sixteen constants itself*** — two
definitions with nothing making them agree, invisible until one TU
included both.
**508 objects with `--disable-all`**, derived from a real configure run
on the host rather than guessed, and `ext/lexbor` is 186 of them —
*`--disable-all` does not mean a small build in 8.5*. **Three things
Expand Down
61 changes: 61 additions & 0 deletions docs/notes/instruments.md
Original file line number Diff line number Diff line change
Expand Up @@ -1148,3 +1148,64 @@ than guessing which sites exist.*
**Its controls** are sections 4 and 5: a constant that fits, and an
unshifted variable that never reaches the fold. Both pass against a
broken compiler.

## `php-mirror.sh` — php's whole build, compiled on the host

`sys/lib/tests/hostkencc/php-mirror.sh`. `--flags` prints the compile
line, `--compile` builds all 514 translation units and **gates**.
Depends on `hostkencc.sh` having run.

**What it came from**: eight consecutive rounds, each costing a VM
rebuild to learn about one compile error, because the error in the next
file is invisible until the first is fixed. Its first run returned all
eight at once, in six files, and all six were fixed and re-measured
without the VM. `docs/notes/php.md` has the list.

***UNLIKE `clisp-mirror.sh` THERE IS NOTHING TO STAGE.*** clisp needs
its `.d` files converted, three `configure` probes run and an
`intparam.h` patched, and that staging is most of its mirror. php's
sources compile where they lie — the generated parsers, scanners and
`zend_vm_execute.h` all ship in the tarball — so this file is the FLAG
SET and the source list and almost nothing else, which is precisely
what the round trips were being spent on.

**Both of those are DERIVED from `cmd/php/mkfile` with sed**, not
transcribed: `DEFS`, `INCS`, `PCRE2FLAGS`, `KECCAKFLAGS` and the
514-entry `OFILES`, with a gate of its own if a variable comes back
unexpanded. *A mirror whose flags have drifted from the build reports
failures the build does not have and misses the ones it does*, which
this tree has paid for twice — clisp's `intparam.h` and the missing
`-J`.

**Its own two faults, both in the first run and both recorded in its
header.** It matched the Keccak flags on the DIRECTORY, which reads
like the mkfile's rule but is not it — there are four explicit Keccak
rules and one of them is `ext/hash/hash_sha3.c`, in `ext/hash` itself
— and it duly reported `Could not find include file "KeccakHash.h"`,
a finding that was the instrument's. And it passed the mkfile's
`CFLAGS` straight through, whose leading **`-c` means "compile only"
to `pcc` and "print `const if`" to `6c`** (`cc/lex.c:40`), which
`pcc` does not forward: dozens of lines per file that read exactly
like diagnostics. *A flag one letter from another flag is checked
rather than recalled, and these two meanings are in two different
programs.*

**What it does not measure is the LINK** — the host has no libap and
`hostkencc` deliberately does not build the linkers — so the
undefined-symbol class stays the VM's.

## `boolptr-test` — a pointer converted to `bool`

`sys/lib/tests/boolptr-test.c`, 0 failures on gcc. The four sites that
CONVERT rather than cast — return, initialiser, assignment, argument —
plus the cast, at three pointer values.

From `Zend/zend_lazy_objects.c:117`, a `void *` returned from a `bool`
function, which kencc refused; `docs/notes/kencc.md` has the mechanism.

***SECTIONS 1 AND 2 ARE WHY IT IS A TEST AND NOT A COMPILE.*** The
plausible wrong fix lets the pointer through as a NUMBER, which keeps
the low byte — so section 2 uses `0x0000555500000000`, non-null and
zero in every byte a `char`, `short` or `int` conversion would keep.
*That is the shape of the perl `cBOOL` bug `boolnorm` was written for,
arriving by a different route.*
145 changes: 145 additions & 0 deletions docs/notes/kencc.md
Original file line number Diff line number Diff line change
Expand Up @@ -3144,3 +3144,148 @@ rule otherwise reads as "do not instrument": the recursion counter is
what proved the walk ran 630 times and visited no label, and that pair
of facts — a working walk and zero labels — is what finally made the
identification untenable.

## Three bugs php's 514 files found, all three in the front end

`sys/lib/tests/hostkencc/php-mirror.sh --compile` compiles php's whole
build with the host-built `6c`. Its first run returned eight errors in
six files; three were this compiler's, and each is a case where the
machinery to do the right thing was already present and reached by
nothing.

**All three are LOUD**, which is why they are recorded as a set rather
than argued over one at a time: none of them can miscompile a program
silently. `agree.c`, the codegen control, is **identical before and
after all three**, and `hostkencc.sh --probe` still answers ten
`regpair-min` and eight `regpair-probe` with `kc` and `vc` failing at
`:242` — the recorded numbers to the digit — so nothing moved that was
not meant to.

### 1. A pointer could not be assigned to a `bool`

`Zend/zend_lazy_objects.c:117` is

static bool zend_lazy_object_has_stale_info(const zend_object *obj)
{
return zend_hash_index_find_ptr(&EG(...), obj->handle);
}

and answered `incompatible types: "UCHAR" and "IND VOID" for op
"RETURN"`. C99 6.5.16.1 lists "the left operand has `_Bool` type and
the right operand is a pointer" as a simple-assignment constraint of
its own, because 6.3.1.2 converts every SCALAR to bool by comparison
with 0.

***THE CONVERSION WAS ALREADY WRITTEN AND SAT BEHIND THE CHECK THAT
REFUSED THE OPERAND.*** `com.c` calls `boolnorm` — `!!x`, a comparison
and not a truncation, the function the perl `cBOOL` bug paid for — at
OAS, OASI, ORETURN and in `tcoma`'s argument case. **Every one of the
four runs `stcompat(..., tasign)` first**, and `tasign[TUCHAR]` is
`BNUMBER`, which excludes BIND; kencc has no TBOOL etype, so the
question never reached the `isbool` mark that the next four lines test.
One clause in `sub.c`'s `stcompat` lets a pointer through when the
target is bool, and all four sites then do what they already meant to.

*The CAST form worked all along* — `tcast[TUCHAR]` carries BIND — which
is why `(bool)p` is everywhere in this tree and nothing had noticed.
`p = b`, an integer assigned to a pointer, is still an error.

`sys/lib/tests/boolptr-test.c` has the four sites at three pointer
values, and **its sections 1 and 2 are the ones that matter**: the
plausible wrong fix lets the pointer through as a NUMBER, which keeps
the low byte, so a pointer whose low four bytes are zero converts to
`false`. `6c -S` shows `CMPQ AX,$0` at all four, and the test is 0
failures on gcc.

### 2. `bool visible;` declared nothing

`ext/reflection/php_reflection.c:3896` is a function whose whole body
is `bool visible;` and a `zend_parse_parameters` of `&visible`, and it
answered `name not declared: visible`. `bool vis;` compiles, which is
what makes the message read as being about the name.

`lex.c` swallows the GNU visibility attributes `hidden` and `visible`
— unprefixed, because the Linux kernel spells them that way — only
when the previous token is NOT a position where an identifier must be a
name. That predicate, `indeclname`, lists `LCHAR`, `LINT`, `LSTRUCT`,
`LUNION`, `LENUM`, `LTYPE` and the rest, and **did not list `LBOOL`**.
So after `bool` the declarator was eaten as an attribute.

The omission is of one member of a set the list already covered:
`LBOOL` is in the grammar's `complex:` rule beside `LSTRUCT`,
`LUNION`, `LENUM` and `LTYPE`, all four of which were listed.
`LCOMPLEX`, `LIMAGINARY` and the internal `LCOMPLEXF`/`LCOMPLEXD` that
`float _Complex` combines to are the rest of that set and were added
from the same reading rather than from a failure.

**The struct form is loud too**: `struct { bool visible; int x; }`
answers `unnamed structure element must be struct/union`, so no member
was ever silently dropped.

***AND A SECOND LIMIT OF THE SAME PREDICATE IS RECORDED AND NOT
FIXED.*** A `;` does not reset `lasttok`, so in

visible int g;
hidden int h;

the second line is not swallowed — `lasttok` is still `LNAME` from `g`
— and answers `not a function`. Measured against the compiler from
before this change and identical, so it predates it. Nothing in the
tree uses the unprefixed spelling at file scope; widening the predicate
would mean swallowing a name in a position where one is legal, which is
the worse failure. In 0.6's inventory.

### 3. The attribute scanner counted parentheses inside string literals

`main/debug_gdb_scripts.c` is a generated file whose entire content is
one `__asm__()` holding 1680 `.ascii` directives — gdb's own python,
inlined into a `.debug_gdb_scripts` section — and it answered

<eof> unexpected EOF in __attribute__

`lex.c` makes `__attribute__`, `__attribute`, `__declspec`,
`__extension__`, `__asm__` and `__asm` invisible to the grammar by
consuming from the opening `(` to the matching `)`, tracking depth. It
did that **without honouring quotes or escapes**, and the gdb text has
one more `(` than `)` in it, so the depth never came back to 0 and the
scanner ate the file. The same hazard is an `__attribute__((section(
"a)b")))` or a `'('` anywhere in an attribute argument. A string or
character literal is now skipped whole, backslash escapes included.

**The message named something the file did not contain**, for the
fourth time in these notes after the `lock.h` shadow, `cmd/mkfile:96`
and the `basename -d` round: all six constructs reported
`__attribute__`. It prints `s->name` now, so the same failure would
have said `__asm__`.

## The host harness lied about a child's exit status

Not a compiler bug, but it was indistinguishable from one for a round
and belongs beside them. One php file made the host `6c` **exit 1,
delete its own output file and print nothing at all** — no diagnostic
on either stream, from a compiler that had just written 46 buffers of
object code.

`strace -f` settled it in one run: the `cpp` child `exit_group(0)`,
the parent's `wait4` returned `WIFEXITED && WEXITSTATUS == 0`, and the
parent then exited 1. The only path that raises `nerrors` without
printing is `lex.c:349`,

if(nerrors == 0 && mywait(&status) > 0 && status != 0)
nerrors++;

and `compat-unix.c`'s `mywait` sets `status` from `w->msg[0]`. The
fault is in `go1.4/src/lib9/await.c`: `w = malloc(sizeof *w + 200)`
followed by `memset(w, 0, sizeof *w)`, which stops at the struct and
leaves the message buffer — part of the same allocation —
**uninitialised**. A child that exits 0 writes nothing to it, so
`msg[0]` was whatever `malloc` last left there, and the answer varied
by input. `memset(w, 0, sizeof *w + 200)` now. The file is
`+build !plan9`, so nothing on the VM reads it and the VM's `mywait`
is `cc/compat.c`'s.

*An instrument that reports a failure the subject does not have is the
one fault that cannot be found by reading the subject* — and this one
also explains why `exits()` losing a buffered `diagbuf` was the first
suspicion and the wrong one: go1.4's `libbio` registers an `atexit`
flush, so a diagnostic would have appeared.
Loading
Loading