Page 1 of 1

Real PHP 8.3 running bare-metal on ESP32-P4 (the actual Zend Engine, not a clone)

Posted: Fri Jul 31, 2026 3:43 pm
by GianfriAur
Sharing a holiday project that has no business existing: I got real PHP 8.3 running bare-metal on an ESP32-P4, no OS under it. Not a "PHP-like" interpreter, not a transpiler, not a toy subset. It's the official php-src 8.3.32 from php.net (sha256 verified, source untouched, every change kept as a separate patch), cross-compiled with riscv32-esp-elf and executed opcode by opcode on the chip's RISC-V core. The full Zend Engine: lexer, parser, compiler, VM, GC, memory manager, objects, exceptions. You drop an index.php on the microSD, press reset, and the board runs it. No recompile.

Why it's even possible comes down to one thing this audience will care about: PSRAM. The engine allocates megabytes of heap, which doesn't fit in a few hundred KB of internal SRAM. The P4-Pico's 32 MB of in-package PSRAM is what makes it fit. Everything else followed from that.

The board is a Waveshare ESP32-P4-Pico (dual-core RISC-V, running at 360 MHz here, 32 MB PSRAM, 32 MB flash, microSD over SDIO). Rigorous engineering rationale for the board choice: I had it lying around.

The interesting part wasn't compiling PHP, it was convincing an engine full of OS assumptions that it's at home on a chip with no OS:
  • No ./configure (its test programs are RISC-V binaries that die on the build host), so php_config.h is hand-written and every POSIX symbol PHP expects but newlib doesn't have is stubbed out.
  • Zend's memory manager thinks in 2 MB blocks, so it's switched off (USE_ZEND_ALLOC=0) and every malloc is routed to PSRAM.
  • The fast VM (computed-goto + global registers) doesn't compile here, so it uses the portable "call" VM. A touch slower, works everywhere.
  • A few surprises that only showed up with the chip on the bench, like the microSD needing its own internal LDO channel powered before it would respond.
GPIO is exposed through a small native extension, so you drive pins straight from PHP. Blink, in full:

Code: Select all

<?php
define('LED', 2);

function setup(): void {
    gpio_mode(LED, GPIO_OUTPUT);
    echo "PHP " . PHP_VERSION . " on ESP32-P4\n";
}

function loop(int $tick): void {
    gpio_write(LED, $tick % 2);
    delay(500);   // vTaskDelay under the hood, so the watchdog stays happy
}
Image

It goes further than blinking a LED. Composer autoloading works with a real vendor/ tree loaded from the card, and SQLite is in: PDO_SQLite with SQLite's own source compiled in, so you get a real SQL database living on the microSD, queries and transactions and all. On top of that, Eloquent (yes, Laravel's ORM) runs against it. A full ORM querying an SQLite database, on a microcontroller. I have no idea why either, but it's genuinely usable: local persistent storage that survives a reboot, which on an edge device is the one thing here that's almost justifiable.

If you want to try it without touching ESP-IDF by hand, there's now a companion CLI, phpflash, a single static Go binary:

Code: Select all

phpflash system-setup      # installs ESP-IDF + the firmware repo
phpflash init my-project   # scaffolds a project (board, extensions, starter sketch)
phpflash build
phpflash flash
phpflash monitor
It reads the boards and extensions from the firmware repo, so adding a board or an extension makes it show up automatically.

Which ESP32s work: the deciding factor isn't the core (it's portable C on the "call" VM, so Xtensa and RISC-V both compile), it's PSRAM plus roomy flash (the image is ~3 MB, so 8 MB+). P4 is the target; ESP32-S3 is the most accessible way to reproduce it (Xtensa, up to 8-16 MB PSRAM, just needs recompiling for the target). WROVER-class ESP32/S2 are borderline. The C and H series have no PSRAM, so they're out regardless of speed.

Honest limits: no networking on this variant (yes, the web's language on a board that doesn't know what a network is; the irony isn't lost on me), so an HTTP/web-server mode is something I'm working on but it isn't there yet. No Fibers either (they'd need 32-bit RISC-V context-switch assembly that doesn't exist), and ext/date is an optional build (~650 KB) rather than on by default.

Firmware + porting notes + the "which chips work" table: https://github.com/php-baremetal/php-esp32
The flash tool: https://github.com/php-baremetal/flash-tool

Next steps:
esp32-p4-eth compatibility for web-server
esp32-s3 family

Happy to go into the memory routing, the newlib stubs, the SQLite build, or the board bring-up if anyone's curious.