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
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,32 @@ silent:
(NTS)`, and `php-smoke.php` is **44 of 45** on the VM -- PHP_INT_SIZE
8, strlen, max, str_repeat at 100k, preg, json, and `/net/tcp/stats`
read as 404 bytes. Notes in `docs/notes/php.md`.**
***AND TWELVE EXTENSIONS ARE BUILT NOW***: the four Phoronix REQUIRES
(libxml, dom, simplexml, zlib -- `system-info` runs) plus eight of the
nine it calls OPTIONAL -- posix, pcntl, bz2, readline, openssl
(LibreSSL), curl, sqlite3, zip. **579 TUs compile and `--undefined`
resolves 1120 symbols** against libap and eight library trees, all of
which come in by `#pragma lib` from their own headers, so `cmd/php`
still has no `LIB=` line. *GD is impossible* (no libgd here; libpng is
not a drawing library) and **`ext/sockets` is deferred to a libap
change**: no `struct cmsghdr`, no `CMSG_*`, no `msg_flags`, `sendmsg`
a stub and **`recvmsg` declared-by-the-header-and-defined-nowhere**.
***THE MEASUREMENT THAT NEARLY SHIPPED WAS AN EMPTY TRANSLATION
UNIT***: five of these wrap their whole body in `#ifdef HAVE_<X>`, so
with the name unanswered they compile to nothing and report 0 errors
-- and *counting `TEXT` symbols does not separate them either*,
because kencc emits php's header `static inline`s that gcc at -O2
discards, putting the floor at ~360. **The discriminator is whether
the assembly mentions `<ext>_module_entry`**: 0 against 10-14, one
grep. Three findings, all upstream's: LibreSSL has no
`sk_X509_new_reserve` (a capacity hint, so `sk_X509_new_null()`);
readline reaches its CLI callbacks through `dlsym`, where APE's
`<dlfcn.h>` would have answered NULL and installed nothing *silently*,
and upstream's own commented-out direct call is the fix; and
`return <void expr>;` in a `void` function, which gcc accepts and C11
6.8.6.4p1 forbids -- fixed in php, **deviation recorded in 0.6 and not
taken**, since `ORETURN` with a left operand means "compute into the
return location" and accepting void is a statement-tree rewrite.
***AND THE SECOND ONE WAS EVERY MULTIPLICATION.***
`zend_multiply.h:135`'s fallback arm -- the one this port takes --
declared the product `long`, which is 64 bits on LP64 and **32
Expand Down
27 changes: 26 additions & 1 deletion docs/notes/instruments.md
Original file line number Diff line number Diff line change
Expand Up @@ -1152,9 +1152,34 @@ 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**.
line, `--compile` builds all 579 translation units and **gates**.
Depends on `hostkencc.sh` having run.

***ITS `--undefined` MODE SAVED EMPTY DIAGNOSTICS FOR A YEAR OF ITS
SHORT LIFE, AND THE REASON IS THE ONE `hostkencc.sh --probe` ALREADY
CARRIES: kencc's `diag()` WRITES TO STDOUT.*** Under `-S` stdout is
where the assembly goes, so a failing unit's errors land at the END OF
THE .s FILE and the `2>` capture holds nothing but cpp's `#warning`
line. A real finding -- LibreSSL's missing `sk_X509_new_reserve` --
read as **exit 1 with no message**, which is indistinguishable from a
compiler crash and was nearly filed as one. The arm now recompiles a
failing unit to an OBJECT, where stdout is the diagnostics and nothing
else. *The same trap, in the same directory, found twice by two
different people-shaped mistakes.*

**`--undefined` needs a `--root` for every library an extension
links**, and this is not optional bookkeeping: with only libap known,
241 of 539 names came back `absent` and the real findings were
invisible among them. Nine roots now. **Its `absent` column has three
standing caveats and the script prints all of them**: DATA symbols
(`stdin`, `environ`, `h_errno`, `xmlParserVersion`, ...), readline's 16
`rl_*` **variables**, and the 10 `BZ2_*`, which are *functions that
are present* -- `bzlib.c` spells every definition
`int BZ_API(BZ2_bzopen)`, so **the name is inside a macro** and no
regex over definition sites can see it. *A sweep that reads source
text cannot see through the preprocessor, and the failure mode is a
confident `absent` naming a function that is right there.*

**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
Expand Down
157 changes: 157 additions & 0 deletions docs/notes/php.md
Original file line number Diff line number Diff line change
Expand Up @@ -1220,3 +1220,160 @@ cannot be found by reading the subject.*
by `apfunc-sweep.py` while answering php's `config.h`. It is the
composition of `localtime_r` and `asctime_r`, both of which were
already there, and is now in `time/ctime.c`.

## The eight optional extensions, and the check that could only pass

Phoronix's `system-info` lists nine OPTIONAL extensions once the four
required ones are in: OpenSSL, GD, Bzip2, SQLite3, PCNTL, POSIX, CURL,
Sockets, Readline. **Eight are built. GD is impossible and Sockets is
deferred**, and the two reasons are different in kind.

Eight of the nine already had a library in this tree — `lib/bz2`,
`lib/readline`+`lib/history`, `lib/libressl`, `lib/curl`, `lib/sqlite`,
`lib/zip` — and POSIX and PCNTL need none. **GD needs libgd, which is
not here**; `lib/png` is, and a PNG codec is not a drawing library, so
this is an absent dependency rather than a port.

### The first measurement was worthless, twice over

Running the mirror over the candidate files reported **posix 1/1,
sqlite3 1/1, zip 2/2, curl 4/4 — clean**. Four of those were real and
*five other extensions reported clean while compiling to nothing at
all*: every one of these extensions wraps its whole body in
`#ifdef HAVE_<X>`, and none of the `HAVE_` names was in the
`config.h`. `ext/posix/posix.c` with `HAVE_POSIX` unanswered is an
empty translation unit, and an empty translation unit compiles.

***A check that cannot fail is not a check***, and this file already
carried that rule for `config.h` answers. What makes it worth writing
down again is that the obvious repair **also failed**: counting `TEXT`
symbols in `6c -S` output gave 363, 368, 397, 452, 470, 486, 505 — for
empty and non-empty units alike — because kencc emits the `static
inline` functions out of php's headers that gcc at `-O2` discards. *The
floor is about 360 and the signal is underneath it.*

**The discriminator that works is one `grep`**: does the assembly
mention `<ext>_module_entry`. 0 for the five empty ones, 10-14 for the
real ones, in a single command. *The question was never "did it
compile" but "did it compile the extension".*

### Three findings, and all three were somebody else's

**LibreSSL has no `*_new_reserve` family at all.**
`openssl_backend_common.c:714` is
`sk_X509_new_reserve(NULL, sk_X509_INFO_num(sk))`, and neither that nor
the `OPENSSL_sk_new_reserve` underneath it exists in LibreSSL 4.3.2.
The call is a **capacity hint and nothing else** — the stack it makes
is filled by `sk_X509_push` in the loop immediately below, and push
grows on demand — so `#define sk_X509_new_reserve(cmp, n)
sk_X509_new_null()` is the same program with one more realloc in it.
It is in `php_openssl_backend.h` under `#if
defined(LIBRESSL_VERSION_NUMBER)`, which is **upstream's own
convention**: php already carries five such guards, one of them in
that same file. *All six openssl files compile after it.*

**`ext/readline` reaches `php_cli_get_shell_callbacks` through
`dlsym`,** and upstream's own commented-out alternative three lines
above is the direct call. Upstream's comment says why the dynamic
lookup is there — even the CGI SAPI links against "CLI"-only
extensions, so the symbol may be absent at run time — and *neither half
of that applies to a build with no dynamic linking*: the extension is
static, `sapi/cli/php_cli.c:111` is in the same binary, and
`sapi/cli/cli.h` is already included.

***The alternative was not "leave it alone".*** APE ships a
`<dlfcn.h>` whose `dlsym` is `return NULL`, so including it compiles,
links, and then installs no callbacks at all — `php -a` without
readline and nothing printed to say so. **A stub that answers "nothing
found" is the trap, not the fix**, which is this tree's oldest rule
arriving through a header nobody added for php. Keyed on
`!defined(HAVE_DLFCN_H)` rather than on Plan 9, so any platform with
dynamic linking keeps upstream's arm untouched.

**`return <void expression>;` in a `void` function.**
`ext/pcntl/pcntl.c:1495` is `return pcntl_signal_dispatch();`, and
kencc answers `incompatible types: "VOID" and "VOID" for op "RETURN"`.
***Both operands being `VOID` is the tell that this is a rule and not
a type mismatch***: C11 6.8.6.4p1 is a constraint and kencc is right,
while gcc and clang accept it silently without `-pedantic`. Fixed as
two statements in php; **the compiler deviation is recorded in 0.6's
inventory and not taken**, with the reason — `ORETURN` with a non-`Z`
left means "compute the value into the return location", so accepting
a void operand is a statement-tree rewrite, not a relaxed check.

### What the answers were derived from

Every function question went to `apfunc-sweep.py` with the library's
own tree as a `--root`; every macro and struct question was a compile
against the real APE headers. The results that mattered:

* libap has **26 of the 29** functions `ext/posix` asks about. Only
`eaccess`, `getsid` and `ttyname_r` are absent.
* `ext/pcntl` gets `fork`, `sigaction`, `sigprocmask`, `sigwaitinfo`,
`sigtimedwait`, `wait3` and `wait4`; not `waitid`, `getpriority`,
`setpriority`, `sched_getcpu`, `unshare`, `forkx` or `getcpuid`.
* **`WIFCONTINUED` exists and `WCONTINUED` does not.** That looks like
a typo and is not — they are separate `#ifdef`s in `pcntl.c` and a
compile answers them separately.
* LibreSSL has neither `RAND_egd` nor `OSSL_set_max_threads`, so no EGD
and no argon2. **The TLS feature macros are not answered in the
`config.h` and must not be**: `xp_ssl.c:70-146` computes
`HAVE_TLS13`, `HAVE_SEC_LEVEL` and the rest from the version macros
itself.
* libzip 1.11.4 has all six functions php version-checks for;
sqlite 3.53.4 has both. **`SQLITE_OMIT_LOAD_EXTENSION` is the Plan 9
answer and not a preference** — `sqlite3_load_extension` is dlopen,
so the alternative is a link error rather than a missing feature.
* `rl_erase_empty_line` is a **variable**, so the sweep cannot see it;
`readline.c:250` defines it and `readline.h:648` declares it.

**All six libraries come in by `#pragma lib` from their own headers**,
checked rather than asserted: `<bzlib.h>`, `<readline/readline.h>`,
`<readline/history.h>`, `<curl/curl.h>`, `<sqlite3.h>`, `<zip.h>` and
the `<openssl/*.h>` set each carry one. `zip.h` carries **six**, which
is libzip's compression backends and its crypto. So there is still no
`LIB=` line in `cmd/php/mkfile`. *`history.h` naming `libreadline.a`
is right rather than a slip*: `lib/readline/mkfile` puts `$HISTOBJ`
into `libreadline.a` as well.

### The state

**579 translation units compile, 0 errors**, and the link wants **1166**
symbols from outside php, of which `--undefined` resolves **1120**
against libap and the eight library trees. The remaining 46 — 40
`absent`, 6 `declared-only` — are all data, assembly or macro-hidden,
and each was opened by hand: the nine known DATA symbols
(`stdin`/`stdout`/`stderr`, `environ`, `h_errno`, `_ctype`,
`_errnoloc`, `xmlParserVersion`, `xmlStringText`), `getcontext`/
`swapcontext`/`signal`/`xmlMalloc`/`zError`/`zlibVersion` as before,
the 16 readline **variables**, and **the 15 `BZ2_*`, which are
functions and are present** — `bzlib.c` spells every definition
`int BZ_API(BZ2_bzopen)`, so the name is inside a macro and no regex
over definition sites can match it. That caveat is
now printed by the mirror itself, with the list, so the next reader
compares against it instead of re-deriving it.

### ext/sockets, and why it is the one deferred

It is not a `config.h` answer. `<sys/socket.h>` has **no `struct
cmsghdr`**, no `CMSG_DATA`/`CMSG_LEN`/`CMSG_SPACE`, no
`MSG_TRUNC`/`MSG_CTRUNC`, and `struct msghdr` has no `msg_flags`;
`multicast.c` additionally `#error`s unless `HAVE_IF_NAMETOINDEX` is
set, which is a free answer since libap has the function. Underneath
that, **`sendmsg` is a stub returning ENOSYS and `recvmsg` is declared
by the header and defined nowhere** — `apfunc-sweep` reports it
`declared`, the middle state that link-fails far from the claim.

What it would take: the `CMSG_*` macros and `struct cmsghdr` are
boilerplate pointer arithmetic; `msg_flags` is a **public struct
change**, so it needs `mk distclean` and an entry beside `-J` and the
64-bit `ino_t`; and `sendmsg`/`recvmsg` can be real for the
scatter/gather case — iterate the iovec over the socket fd — with
ancillary data the only part that genuinely cannot work, since Plan 9
has no `SCM_RIGHTS`. **That last part is the one to get right**: an
`fd`-passing call must fail loudly rather than silently drop the
control message.

Phoronix wants sockets only "when running the Phoromatic Server", so
it is the least valuable of the nine and the only one needing a public
struct to move. *Deferred on cost, not on possibility.*
53 changes: 53 additions & 0 deletions docs/releases/0.6.md
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,33 @@ distinction is the point of the list.

### Compiler

- **`return <void expression>;` in a `void` function is REJECTED, and
gcc and clang accept it.** `com.c:224`'s `ORETURN` case runs
`tcompat(n, n->type, l->type, tasign)` whatever the types are, so a
void operand returned from a void function answers

pcntl.c:1495 incompatible types: "VOID" and "VOID" for op "RETURN"

***Both operands being `VOID` is the tell***: this is a rule, not a
type mismatch. **kencc is strictly right** — C11 6.8.6.4p1 is a
constraint, "A return statement with an expression shall not appear
in a function whose return type is void" — but gcc and clang accept
it silently without `-pedantic`, so the construct is common in real
C and this is a portability deviation rather than a correctness one.
**MEASURED**: php 8.5.12's `ext/pcntl/pcntl.c:1495`, which is
`return pcntl_signal_dispatch();` in a `static void` function;
fixed in php as two statements, which is the same program.
**Why it is recorded and not taken**: accepting it can only widen
what compiles — there is no ABI consequence and no codegen change
for anything that already builds — but the fix is not a predicate
edit. `ORETURN` with a non-`Z` left means "compute the value into
the return location", so a void operand has to be restructured into
"evaluate the expression, then return", which is a statement-tree
rewrite inside `tcom`'s expression walk rather than a relaxed check.
*One line of php against a structural change in the shared front
end, on the last day of a port* — the php side was taken for that
reason, and the construct will recur.

- **`mkvar` keys an optimizer variable on its OFFSET, so a type pun
deletes the store -- FIXED IN `6c` ONLY, LIVE IN THE OTHER NINE.**
`[1-9]c/reg.c`'s `mkvar` keys on (sym, name, **offset**), so
Expand Down Expand Up @@ -1362,6 +1389,32 @@ distinction is the point of the list.

### libap

- **`recvmsg` is declared by `<sys/socket.h>` and defined NOWHERE, and
`sendmsg` is a stub that returns ENOSYS.** `apfunc-sweep.py` reports
`recvmsg` as `declared` — the middle state the sweep exists for, and
the one that link-fails a long way from the claim, since a
`config.h` or a header can say "yes" to it right up until `6l` runs.
`network/sendmsg.c` is fourteen lines and its own comment says Plan
9 "does not natively support the complex POSIX sendmsg/recvmsg
interface based on msghdr".
**The header is missing the rest of the interface too**: no `struct
cmsghdr`, no `CMSG_DATA`/`CMSG_LEN`/`CMSG_SPACE`/`CMSG_FIRSTHDR`/
`CMSG_NXTHDR`, no `MSG_TRUNC`/`MSG_CTRUNC`, and `struct msghdr` has
no `msg_flags`.
**MEASURED** by php 8.5.12's `ext/sockets`, which is the reason the
one extension of nine that Phoronix asks for is not built: three of
its five files fail to compile and the other two would fail to link.
**What it would take, and why it is not free**: the `CMSG_*` macros
and `struct cmsghdr` are boilerplate pointer arithmetic, and
`sendmsg`/`recvmsg` can be *real* for the scatter-gather case by
iterating the iovec over the socket fd — but `msg_flags` is a
**public struct change**, so it joins `-J` and the 64-bit
`ino_t`/`dev_t` in needing `mk distclean`, and **ancillary data
genuinely cannot work**: Plan 9 has no `SCM_RIGHTS`, so an
fd-passing call must fail loudly rather than silently drop the
control message. *That last part is the whole risk* — this list's
most common bug is a stub that answers the wrong kind of nothing.

- **Every architecture's `fenv.s` is inert, so the fenv API is a stub on
all of them** — **measured**, and the most consequential thing the 386
cross build has turned up. `as` rendered musl's `.globl name` and
Expand Down
Loading
Loading