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
28 changes: 21 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1230,15 +1230,29 @@ 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`
***AND THIRTEEN EXTENSIONS ARE BUILT NOW***: the four Phoronix
REQUIRES (libxml, dom, simplexml, zlib -- `system-info` runs), eight
of the nine it calls OPTIONAL -- posix, pcntl, bz2, readline, openssl
(LibreSSL), curl, sqlite3, zip -- **and gd, which was the ninth and
was impossible until `lib/gd` existed**. php takes upstream's
EXTERNAL arm, so it is `gd.c` + `gd_compat.c`, **two objects and not
the forty of php's bundled libgd** -- which is also the copy not
worth keeping, since it announces `2.0.35` while carrying AVIF, WebP
and the interpolation code. *`HAVE_LIBGD` is the gate, not
`HAVE_GD`*, and the first probe got the empty-TU result again.
***ITS FORMAT ANSWERS ARE A CONFIGURE PROBE THAT WOULD LIE, THE
FOURTH OF THAT FAMILY***: php asks the installed libgd what it can do
by LINKING `gdImageCreateFromJpeg`/`...Avif`/`...Webp`/`...Xpm`, and
**all four link** -- upstream compiles those files with no library
present and stubs each to `gd_error("... has been disabled")`. `nm`
over the archive says so. Answered **no** by hand, because otherwise
`function_exists('imagejpeg')` and `imagetypes() & IMG_JPG`, the two
documented ways to ask, both lie. *`HAVE_GD_FREETYPE` is the one
where probe and truth agree* -- `gdFontCacheShutdown` is genuinely
absent. **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`
still has no `LIB=` line. **`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
Expand Down
110 changes: 108 additions & 2 deletions docs/notes/php.md
Original file line number Diff line number Diff line change
Expand Up @@ -1225,8 +1225,12 @@ cannot be found by reading the subject.*

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.
Sockets, Readline. **Eight were built in that round and GD followed
once `lib/gd` existed; only Sockets is still deferred.** *This
paragraph said "GD is impossible" and that was true only of the tree
as it stood* -- php bundles a libgd and the dependencies were already
here, so the sentence was about a missing vendored library rather
than about the platform. See the ext/gd section at the end.

Eight of the nine already had a library in this tree — `lib/bz2`,
`lib/readline`+`lib/history`, `lib/libressl`, `lib/curl`, `lib/sqlite`,
Expand Down Expand Up @@ -1442,3 +1446,105 @@ phrase occurs nowhere under `sys/src`. It is the 9front kernel's own
advisory as it grows a process's fd table, and it is a notice rather
than a limit. Worth a second look only if the number keeps climbing,
which would be a leak rather than a large working set.

## ext/gd, against the external libgd — and a configure probe that lies

With `lib/gd` building a real `libgd.a` (2.3.3, vendored at
`sys/src/external/libgd`), php takes **upstream's external arm**:
`ext/gd/gd.c` plus the `gd_compat.c` shim, **two objects instead of
forty**. 581 translation units compile, 0 errors.

**The bundled copy is the one to not keep.** php ships its own libgd
under `ext/gd/libgd`, and building it would put a second, divergent
copy in a tree that now has the real one — and php's is the worse of
the two to carry: it still announces `GD_VERSION_STRING "2.0.35"`
while carrying `gd_avif.c`, `gd_webp.c`, `gd_bmp.c` and the whole
interpolation file, none of which existed in 2.0.35.

**`HAVE_LIBGD` is the gate, not `HAVE_GD`.** `php_gd.h:24` is
`#if defined(HAVE_LIBGD) || defined(HAVE_GD_BUNDLED)`, and with
neither name the header defines `phpext_gd_ptr` as `NULL` and `gd.c`
compiles to nothing. The first probe used `-DHAVE_GD=1` and got the
empty-translation-unit result that five extensions already produced
once — *caught by the same `<ext>_module_entry` grep*, which reports
11 references now.

### The format answers are a configure probe that would lie

php's external arm asks the installed libgd what it can do by
**linking** `gdImageCreateFromJpeg`, `...Avif`, `...Webp` and
`...Xpm`. **All four link.** `nm` over the archive this tree builds:

```
gdImageCreateFromPng DEFINED gdImageCreateFromJpeg DEFINED
gdImageCreateFromAvif DEFINED gdImageCreateFromXpm DEFINED
gdImageCreateFromWebp DEFINED gdFontCacheShutdown absent
```

Upstream libgd compiles `gd_jpeg.c`, `gd_avif.c`, `gd_webp.c` and
`gdxpm.c` **even with no such library** and gives each a stub:
`gd_jpeg.c:1209`'s `_noJpegError()` calls
`gd_error("JPEG image support has been disabled")` and returns. *So
the probe succeeds and the capability is absent* — which is
`HAVE_WORKING_MPROTECT`, `fsync` and `statvfs` in their exact shape, a
fourth time, and the first where the stub is **upstream's deliberate
design** rather than a gap in this tree.

They are answered **no** by hand, and the difference reaches scripts
rather than being academic: with them yes, php registers
`imagejpeg()` and sets the bit in `imagetypes()`, so
`function_exists('imagejpeg')` and `imagetypes() & IMG_JPG` — the two
documented ways to ask what gd can do — would **both lie**, and the
failure would surface later as a false return from a function that
should not exist.

**`HAVE_GD_FREETYPE` is the one where probe and truth agree**: php
checks `gdFontCacheShutdown`, which exists only inside `gdft.c`'s
`HAVE_LIBFREETYPE` arm and so is genuinely absent from the archive.
The three yes answers — `HAVE_GD_PNG`, `HAVE_GD_LIBVERSION`,
`HAVE_GD_GET_INTERPOLATION` — are link-level facts from the same `nm`.

### The link, and 110 names that look missing and are not

`--undefined` reports **`defined 1121, declared-only 116, absent 45`**
over 581 translation units. The `declared-only` figure jumped from 6
to 116, and **all 110 new ones are `gd*`**.

They are not missing. libgd writes every definition as

```c
BGD_DECLARE(gdImagePtr) gdImageCreateTrueColor (int sx, int sy)
```

so the **return type** sits inside a macro call, and a
definition-shaped regex expecting `type name(` cannot match it. That
is the `BZ2_*` caveat arriving from the other side: *bzip2 hides the
NAME in a macro, libgd hides the RETURN TYPE* — two ways to blind the
same regex, and the sweep's own header already says to read this
column rather than gate on it.

**Checked rather than assumed**: `nm -g --defined-only` over the
host-built `libgd.a` against those 110 names gives **110 defined, 0
missing**. The mirror prints the caveat and that number now, so the
next reader compares instead of re-deriving.

*An unrelated caution learned here*: re-running `apfunc-sweep.py` by
hand with only the new `--root` and dropping the other nine reported
**923** declared-only, which looks exactly like a catastrophic
finding and is just a short command line.

### What the smoke test adds

`php-smoke.php` grows a gd block that is a **round trip, not a
version string**: draw into a truecolour image, read a pixel back,
`imagepng()` to an output buffer, check the 8-byte signature,
`imagecreatefromstring()` it back, and compare the pixel and the
width. That exercises `ext/gd`, `libgd.a`, `libpng.a` and `libz.a`
together, and it is what says `lib/gd` actually linked.

**The two capability assertions are Plan9-only**, by Section 7's
rule, and *the host proved why*: a build host with a full libgd
reports JPEG yes and failed both of them — which is the control
working, not a bug. "JPEG is absent" is a statement about this port's
libgd, not about php or about gd. 0 failures on host php 8.3.6, which
happens also to ship GD 2.3.3.
2 changes: 1 addition & 1 deletion docs/notes/plas_stack_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ objects into `OFILES`, the `phpext_*_ptr` into
and a rebuild.

**Not built**: PDO and `pdo_sqlite` (use the `SQLite3` class instead),
mbstring, GD (there is no libgd in the tree), and `ext/sockets` (which
mbstring and `ext/sockets` (which
needs `struct cmsghdr` and a real `recvmsg` in libap).

To check the build on the machine:
Expand Down
10 changes: 9 additions & 1 deletion sys/lib/tests/hostkencc/php-mirror.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ EOF
n=$(wc -l < "$OUT/undefined")
echo "php-mirror: $n symbol(s) the link must find outside php"
echo "php-mirror: the list is in $OUT/undefined; asking libap --"
# ***NINE LIBRARIES NOW, NOT ONE.*** php pulls each of them
# ***TEN LIBRARIES NOW, NOT ONE.*** php pulls each of them
# through `#pragma lib' in its own header, so a sweep that knows
# only libap answers `absent' for every `xmlParseFile',
# `deflateInit_', `SSL_new' and `curl_easy_init' -- 241 of the
Expand All @@ -319,6 +319,7 @@ EOF
--root sys/src/external/libzip \
--root sys/src/external/readline \
--root sys/src/external/bzip2 \
--root sys/src/external/libgd \
--file "$OUT/undefined" |
grep -v ' *defined ' || true
echo "php-mirror: ^ anything here is READ, not a failure -- the"
Expand All @@ -333,6 +334,13 @@ EOF
echo "php-mirror: the NAME IS INSIDE A MACRO and no regex over"
echo "php-mirror: definition sites can see it. **A name here"
echo "php-mirror: that is not on that list is the finding.**"
echo "php-mirror: Likewise 110 'declared' gd* names: libgd"
echo "php-mirror: writes every definition as"
echo "php-mirror: BGD_DECLARE(gdImagePtr) gdImageCreateTrueColor(...)"
echo "php-mirror: so the RETURN TYPE is inside a macro where BZ2_"
echo "php-mirror: hides the NAME -- two ways to blind the same"
echo "php-mirror: regex. All 110 were checked with nm against the"
echo "php-mirror: host-built libgd.a: 110 of 110 defined, 0 missing."
exit 0
fi
echo "php-mirror: compiled $ok, errors $bad, missing $missing"
Expand Down
56 changes: 55 additions & 1 deletion sys/lib/tests/php-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ function eq($what, $got, $want) {
$want = ['core', 'date', 'pcre', 'hash', 'json', 'random', 'Reflection',
'SPL', 'standard', 'libxml', 'dom', 'SimpleXML', 'zlib',
'posix', 'pcntl', 'bz2', 'readline', 'openssl', 'curl',
'sqlite3', 'zip'];
'sqlite3', 'zip', 'gd'];
$missing = [];
foreach ($want as $e) {
if (!extension_loaded($e)) $missing[] = $e;
Expand Down Expand Up @@ -416,5 +416,59 @@ function eq($what, $got, $want) {
echo " INFO readline " . $i['library_version'] . "\n";
}

/* gd -- against the EXTERNAL libgd, so this also proves lib/gd links. */
if (extension_loaded('gd')) {
$i = gd_info();
echo " INFO GD Version: " . ($i['GD Version'] ?? '?') . "\n";
/*
* ***THE CAPABILITY FLAGS ARE THE POINT OF THIS BLOCK, NOT THE
* DRAWING.*** libgd compiles stub entry points for JPEG, WebP,
* AVIF and XPM even with no such library -- they LINK and then
* call `gd_error("... has been disabled")' -- so a configure
* probe answers yes and `imagetypes()' would lie. APExp answers
* those `HAVE_GD_*' no by hand, and these two lines are what
* says the hand answer reached PHP.
*/
eq("PNG support is reported", (bool)(imagetypes() & IMG_PNG), true);
/*
* Asserted on Plan9 ONLY, by Section 7's rule: "JPEG is absent"
* is a statement about THIS PORT's libgd, not about php or gd,
* and a build host with a full libgd fails it while being
* perfectly correct. It did -- which is the control working.
*/
if (PHP_OS === 'Plan9') {
eq("JPEG is NOT reported (libgd has only a stub)",
(bool)(imagetypes() & IMG_JPG), false);
eq("imagejpeg() is absent rather than failing later",
function_exists('imagejpeg'), false);
} else {
printf(" INFO not on Plan9; this host reports JPEG=%s\n",
(imagetypes() & IMG_JPG) ? 'yes' : 'no');
}

/* A real round trip: draw, encode to PNG, decode, read a pixel. */
$im = imagecreatetruecolor(40, 30);
eq("imagecreatetruecolor", $im !== false, true);
if ($im !== false) {
$col = imagecolorallocate($im, 0x20, 0x90, 0xd0);
imagefilledrectangle($im, 0, 0, 39, 29, $col);
eq("pixel reads back", imagecolorat($im, 10, 10) & 0xffffff, 0x2090d0);
ob_start();
$wrote = imagepng($im);
$png = ob_get_clean();
eq("imagepng wrote something", $wrote && strlen($png) > 50, true);
eq("PNG signature", substr($png, 0, 8), "\x89PNG\r\n\x1a\n");
$im2 = imagecreatefromstring($png);
eq("decoded the PNG back", $im2 !== false, true);
if ($im2 !== false) {
eq("round-tripped width", imagesx($im2), 40);
eq("round-tripped pixel",
imagecolorat($im2, 10, 10) & 0xffffff, 0x2090d0);
imagedestroy($im2);
}
imagedestroy($im);
}
}

printf("\nphp-smoke: %d failures\n", $fails);
exit($fails);
61 changes: 59 additions & 2 deletions sys/src/ape/cmd/php/cfg/main/php_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,9 @@
* four required ones are in. Eight of them have a library in this
* tree already -- `lib/bz2', `lib/readline'+`lib/history',
* `lib/libressl', `lib/curl', `lib/sqlite', `lib/zip' -- and two need
* no library at all. **GD is the one that is impossible**: there is
* no libgd here, only libpng, and libpng is not a drawing library.
* no library at all. GD was the ninth and needed a libgd this tree
* did not have at the time; **it does now** (`lib/gd', 2.3.3), and
* `ext/gd' is answered further down.
*
* ***FIVE OF THE EIGHT "COMPILED CLEAN" BEFORE A SINGLE LINE BELOW
* EXISTED, AND THAT WAS AN EMPTY TRANSLATION UNIT EACH TIME.*** Every
Expand Down Expand Up @@ -534,6 +535,62 @@
#define HAVE_CANCEL_CALLBACK 1
#define HAVE_METHOD_SUPPORTED 1

/*
* ---- ext/gd, against the EXTERNAL libgd (`lib/gd', 2.3.3) --------
*
* **`HAVE_GD_BUNDLED' is deliberately NOT defined.** php ships its
* own copy of libgd under `ext/gd/libgd' and would build 40 objects
* from it; this tree has a real `libgd.a' now, so php takes the
* external arm and builds TWO -- `gd.c' and `gd_compat.c'. That
* avoids a second, divergent libgd in the tree, and php's bundled
* copy is the worse one to keep: it announces `GD_VERSION_STRING
* "2.0.35"' while carrying AVIF, WebP, BMP and the interpolation
* code, none of which existed in 2.0.35.
*
* ***`HAVE_LIBGD' IS THE GATE, NOT `HAVE_GD'.*** `php_gd.h:24' is
* `#if defined(HAVE_LIBGD) || defined(HAVE_GD_BUNDLED)', so with
* neither name the file declares `phpext_gd_ptr NULL' and `gd.c'
* compiles to nothing -- the empty-translation-unit shape that cost
* a measurement on five extensions already.
*
* ***AND THE FORMAT ANSWERS ARE WHERE A WORKING configure WOULD GET
* THIS WRONG, FOR THE FOURTH TIME.*** php probes the installed
* libgd by LINKING `gdImageCreateFromJpeg', `...Avif', `...Webp' and
* `...Xpm'. **All four link.** Upstream libgd compiles
* `gd_jpeg.c', `gd_avif.c', `gd_webp.c' and `gdxpm.c' even with no
* such library and gives each a stub -- `gd_jpeg.c:1209's
* `_noJpegError()' calls `gd_error("JPEG image support has been
* disabled")' and returns. *So the probe succeeds and the
* capability is absent*, which is `HAVE_WORKING_MPROTECT' and
* `fsync' and `statvfs' in their exact shape.
*
* They are answered NO, and the difference is visible to scripts
* rather than academic: with them yes php registers `imagejpeg()'
* and sets the bit in `imagetypes()', so `function_exists()' and
* `imagetypes() & IMG_JPG' -- the two documented ways to ask what gd
* can do -- would both LIE, and the failure would arrive later as a
* false return from a function that should not exist.
*
* The three yes answers are link-level facts, checked with `nm' over
* the archive this tree builds rather than assumed:
* `gdImageCreateFromPng', `gdVersionString' and
* `gdImageGetInterpolationMethod' are all defined.
* **`HAVE_GD_FREETYPE' is a genuine link-level NO**: php probes
* `gdFontCacheShutdown', which exists only inside gdft.c's
* `HAVE_LIBFREETYPE' arm, so it is absent from the archive -- the
* one format-ish answer where the probe and the truth agree.
*/
#define HAVE_LIBGD 1
#define HAVE_GD_PNG 1
#define HAVE_GD_LIBVERSION 1
#define HAVE_GD_GET_INTERPOLATION 1
/* #undef HAVE_GD_BUNDLED */
/* #undef HAVE_GD_JPG */
/* #undef HAVE_GD_AVIF */
/* #undef HAVE_GD_WEBP */
/* #undef HAVE_GD_XPM */
/* #undef HAVE_GD_FREETYPE */

/*
* ---- ext/sockets is NOT built, and the reason is in libap rather
* than in php. `<sys/socket.h>' has no `struct cmsghdr', no
Expand Down
14 changes: 11 additions & 3 deletions sys/src/ape/cmd/php/internal_functions_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "ext/curl/php_curl.h"
#include "ext/sqlite3/php_sqlite3.h"
#include "ext/zip/php_zip.h"
#include "ext/gd/php_gd.h"


static zend_module_entry * const php_builtin_extensions[] = {
Expand Down Expand Up @@ -100,9 +101,9 @@ static zend_module_entry * const php_builtin_extensions[] = {
* pcre)' -- and pcre is the second entry in this array, so the
* order below is free and is the order the mkfile lists them in.
*
* GD is the ninth and is absent because there is no libgd in
* this tree; ext/sockets is the tenth and is deferred to a libap
* change. Both are explained in `php_config.h'.
* ext/sockets is the one still missing, deferred to a libap
* change; `php_config.h' explains it. gd is below, against the
* external `lib/gd'.
*/
phpext_posix_ptr,
phpext_pcntl_ptr,
Expand All @@ -112,6 +113,13 @@ static zend_module_entry * const php_builtin_extensions[] = {
phpext_curl_ptr,
phpext_sqlite3_ptr,
phpext_zip_ptr,
/*
* gd against the EXTERNAL libgd (`lib/gd'), not php's bundled
* copy -- `php_config.h' leaves HAVE_GD_BUNDLED undefined, and
* the gate for this pointer is HAVE_LIBGD. It declares no
* dependency on another extension.
*/
phpext_gd_ptr,

};

Expand Down
Loading
Loading