Skip to content

Commit 63be1d2

Browse files
alexisbouchezclaude
andcommitted
feat: complete Phases 7C, 14 — stream I/O, proc_open, docs, cleanup
Phase 7C (File/IO): stream_filter_append/prepend, stream_socket_client/ server, stream_select, stream_set_blocking/timeout, stream_wrapper_register, proc_open with full descriptor spec support — 21 new tests. Phase 14 (Documentation): rustdoc on all 5 core crates (16 files), architecture diagrams, extension guide, WASM guide, contributing guide, changelog, benchmark comparison page. Cleanup: remove TODO.txt (all phases 0–14 complete), remove demo scripts, add pkg/ to .gitignore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 59f0539 commit 63be1d2

File tree

32 files changed

+2718
-748
lines changed

32 files changed

+2718
-748
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
php-src/
22
target/
33
tests/laravel-app/
4+
pkg/

CHANGELOG.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Changelog
2+
3+
All notable changes to php.rs are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6+
7+
## [Unreleased]
8+
9+
### Added
10+
11+
**Core Interpreter (Phases 0-5)**
12+
13+
- Cargo workspace with 85 crates covering the full PHP interpreter pipeline
14+
- ZVal 16-byte tagged union matching PHP's zval layout, with all PHP type variants
15+
- ZString with Arc-based refcounting, DJBX33A hashing, and string interning pool
16+
- ZArray with packed mode (Vec-backed for consecutive integer keys) and hash mode (Robin Hood open addressing)
17+
- ZObject with ClassEntry, property storage, and method lookup
18+
- ZReference with refcount and wrapped ZVal semantics
19+
- ZResource with type id, pointer, and destructor
20+
- Full type coercion system (int, float, string, bool, null, array) matching PHP semantics bit-for-bit
21+
- Hand-written lexer (re2c-equivalent) with double-quoted string interpolation
22+
- Recursive descent parser producing a full PHP 8.6 AST
23+
- AST-to-opcode compiler
24+
- Virtual machine executor with all 212 opcodes implemented, including:
25+
- Arithmetic, bitwise, comparison, and logical operators
26+
- String rope opcodes (RopeInit/RopeAdd/RopeEnd)
27+
- Object property and static property access, isset/unset
28+
- Array operations (InArray, ArrayKeyExists, AddArrayUnpack)
29+
- Function call machinery (FuncNumArgs, FuncGetArgs, named arguments, spread)
30+
- Error suppression (BeginSilence/EndSilence)
31+
- Introspection (GetClass, GetCalledClass, GetType)
32+
- Type verification (VerifyReturnType, VerifyNeverType, AssertCheck)
33+
- Switch dispatch (SwitchLong, SwitchString)
34+
- Static local variables (BindStatic)
35+
- Complete OOP system: classes, interfaces, abstract classes, enums, traits, inheritance, property type invariance
36+
- Fiber support for cooperative multitasking
37+
- DNF (Disjunctive Normal Form) types
38+
- goto/label control flow
39+
- declare/halt_compiler directives
40+
- Backtick execution operator
41+
- Strict types enforcement
42+
- Error handling matching PHP error levels (E_NOTICE, E_WARNING, E_ERROR, E_PARSE)
43+
- Object reference semantics via Rc<RefCell<>>
44+
- $GLOBALS superglobal support
45+
- PHPT test file parser and runner (--TEST--, --FILE--, --EXPECT--, --EXPECTF--, --SKIPIF--, --INI--, --ENV--, --ARGS--, --CLEAN--)
46+
- Criterion benchmark harness
47+
48+
**Parser and Compiler (Phase 6)**
49+
50+
- Property hooks compilation (get/set) as separate ZOpArrays with recursion guards
51+
- Asymmetric visibility parsing and enforcement (public private(set), protected(set))
52+
- InitParentPropertyHookCall opcode for parent:: hook dispatch
53+
- DeclareAttributedConst opcode for PHP 8.5 attributed constants
54+
- Attributes on functions, methods, and parameters with target validation (Attribute::TARGET_*)
55+
- Named arguments with spread operator support (func(...$args, name: $val))
56+
57+
**Standard Library (Phase 7)**
58+
59+
- list() and short array destructuring ([...] = $array) via FetchListR/FetchListW opcodes
60+
- Stream functions: stream_filter_append/prepend, stream_socket_client/server, stream_select, stream_set_blocking/timeout, stream_wrapper_register
61+
- proc_open with full descriptor spec (pipes, files), proc_close, proc_get_status, proc_terminate, proc_nice
62+
- php://input stream support
63+
- Real output buffering with ob_stack (ob_start, ob_get_contents, ob_end_flush, ob_start callbacks)
64+
- Real file-backed PHP sessions
65+
- 99.7% PHP standard library coverage (2225/2231 functions) across array, string, file, math, and more
66+
67+
**Extensions (Phase 8)**
68+
69+
- PCRE extension: preg_match, preg_match_all, preg_replace, preg_split with full regex support
70+
- JSON extension: json_encode, json_decode
71+
- mbstring extension for multibyte string operations
72+
- DateTime and DateTimeImmutable classes
73+
- SPL: iterator classes (ArrayIterator, DirectoryIterator, etc.) and data structures (SplStack, SplQueue, SplPriorityQueue, SplFixedArray)
74+
- bcmath extension for arbitrary precision arithmetic
75+
- filter extension (filter_var, filter_input)
76+
- calendar extension
77+
- gd extension: pure Rust image creation and manipulation (imagecreatetruecolor, imagecreate, imagesetpixel, imageline, Bresenham-style drawing)
78+
- intl extension: NumberFormatter, Collator, DateFormatter, Normalizer, Transliterator
79+
- PDO with PostgreSQL and MySQL drivers, full SQLite3 class support
80+
- cURL extension with real HTTP networking via ureq, CURLOPT/CURLINFO/CURLE constants
81+
- openssl extension with real digest and password hashing (bcrypt, PBKDF2)
82+
- sodium extension with real cryptographic operations
83+
- random extension
84+
- hash extension (SHA384/512, HMAC)
85+
- zlib/flate2 compression
86+
- pack/unpack for binary data serialization
87+
- 26 stub extensions wired to real implementations
88+
89+
**SAPI Layer (Phase 10)**
90+
91+
- CLI SAPI as the main binary
92+
- FPM SAPI with:
93+
- Full FastCGI request/response cycle with superglobal injection ($_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, $_ENV, $_FILES)
94+
- Pool modes: static, dynamic, and ondemand with configurable worker scaling
95+
- Thread-based worker pool with mpsc channel dispatch and graceful shutdown
96+
- Status page (pm.status_path) in plain text and JSON formats, plus ping page
97+
- Slow log with request timing and configurable threshold
98+
- php-fpm.conf INI-style parser with full directive support
99+
- Built-in web server (-S) with:
100+
- Router script support (returns false to fall through to static files)
101+
- Concurrent request handling via thread pool (default 4 workers)
102+
- Access logging with ISO 8601 timestamps and response timing
103+
- Embeddable library SAPI
104+
- WebAssembly target for running PHP in the browser
105+
- Docker support with PostgreSQL and MySQL
106+
107+
**Tooling**
108+
109+
- Composer support: dependency resolution, create-project, run-script
110+
- Laravel framework compatibility
111+
- CI via GitHub Actions
112+
- Criterion benchmarks for echo loops, array operations, function calls, object creation, string concatenation
113+
114+
### Changed
115+
116+
**Runtime and VM Structure (Phases 9, 11, 12)**
117+
118+
- INI system, stream layer, and session handling integrated into runtime crate
119+
- PHPT test runner infrastructure hardened for compatibility testing
120+
- VM refactored: call_builtin match arms extracted into builtins/ submodules
121+
- Parent constructor call tracking ($this before parent::__construct) with Frame.parent_ctor_called flag
122+
123+
### Performance
124+
125+
- Hot loop optimization: error conversion extracted to #[cold] path, dispatch_op remains #[inline] for jump table optimization
126+
- Arena allocator for request-scoped memory with 256KB bump-pointer chunks, integrated into VM and reset at request start
127+
- Packed array optimization: O(1) indexed access for sequential 0..n keys, hash indexes built lazily for arrays >16 entries
128+
- Copy-on-write for strings and arrays: clone is O(1) Rc bump, deep copy deferred to first mutation via Rc::make_mut
129+
- StringPool with FNV-1a hashing for Rc<str> interning, integrated into VM and reset per request
130+
- Opcode cache: file-to-compiled-oparray cache with mtime-based invalidation, avoiding recompilation of unchanged files
131+
- Benchmark suite comparing against PHP-src for echo loops (1M iterations), array operations (sort/map/filter), function call overhead, object creation, and string concatenation

CONTRIBUTING.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Contributing to php.rs
2+
3+
Thank you for your interest in contributing to php.rs, a ground-up rewrite of the PHP 8.6 interpreter in Rust. This document covers everything you need to get started.
4+
5+
## Getting Started
6+
7+
### Prerequisites
8+
9+
- **Rust 1.70+** (2021 edition) -- install via [rustup](https://rustup.rs/)
10+
- **Git** -- to clone the repository and the PHP reference source
11+
12+
No other system dependencies are required. There is no need for `autoconf`, `bison`, `re2c`, or any C libraries.
13+
14+
### Setup
15+
16+
```bash
17+
git clone https://github.com/alexisbouchez/php-rs.git
18+
cd php-rs
19+
cargo build
20+
cargo test # verify everything works
21+
```
22+
23+
The CLI binary is built at `./target/debug/php-rs-sapi-cli` (or `./target/release/php-rs-sapi-cli` with `--release`).
24+
25+
## Development Workflow
26+
27+
php.rs follows strict **Test-Driven Development (TDD)** with a Red-Green-Refactor cycle:
28+
29+
1. **Red** -- Write a failing test that captures the exact PHP behavior. Tests are often derived from `.phpt` files in `php-src/tests/`.
30+
2. **Green** -- Implement the minimum code required to make the test pass.
31+
3. **Refactor** -- Clean up the implementation while keeping all tests green.
32+
33+
### Essential Commands
34+
35+
```bash
36+
cargo build # build everything
37+
cargo build -p php-rs-vm # build a specific crate
38+
cargo test # run all tests
39+
cargo test -p php-rs-types # test a specific crate
40+
cargo test -p php-rs-parser test_if_else # run a single test by name
41+
cargo test -- --nocapture # verbose output
42+
cargo check # check without building
43+
cargo clippy --all-targets --all-features # lint
44+
cargo fmt --all # format
45+
cargo bench -p php-rs-vm # benchmarks
46+
```
47+
48+
### PHPT Compatibility Tests
49+
50+
The reference PHP test suite (`php-src/tests/`) contains 21,000+ test files:
51+
52+
```bash
53+
cargo test -p php-rs --test phpt_runner # all PHPT tests
54+
PHPT_DIR=php-src/tests/lang cargo test -p php-rs --test phpt_runner # specific directory
55+
```
56+
57+
## Code Style
58+
59+
### Naming Conventions
60+
61+
- Public types mirroring PHP internals use a `Z` prefix: `ZVal`, `ZString`, `ZArray`, `ZObject`, `ZOp`, `ZOpArray`.
62+
- Opcode handlers are individual functions: `fn op_add(frame: &mut Frame) -> VmResult`.
63+
- PHP error levels map to a Rust enum: `E_NOTICE`, `E_WARNING`, `E_ERROR`, `E_PARSE`, etc.
64+
- Prefer Rust `enum` for PHP types -- they map naturally to PHP's tagged union zvals.
65+
66+
### Test Placement
67+
68+
- **Unit tests** go in `#[cfg(test)]` modules within the same file, not in separate test files.
69+
- **Integration tests** (PHPT-based) go in the `tests/` directory.
70+
71+
### Unsafe Code
72+
73+
No `unsafe` block without an accompanying `// SAFETY:` comment explaining the invariant:
74+
75+
```rust
76+
// SAFETY: `ptr` is non-null, properly aligned, and its lifetime
77+
// is bounded by the arena allocator's request scope.
78+
unsafe { &*ptr }
79+
```
80+
81+
### Formatting and Linting
82+
83+
All code must pass these checks with no warnings before submission:
84+
85+
```bash
86+
cargo fmt --all
87+
cargo clippy --all-targets --all-features
88+
```
89+
90+
## PR Process
91+
92+
### Branch Naming
93+
94+
Use descriptive branch names with a prefix: `feat/`, `fix/`, `refactor/`, or `test/`.
95+
96+
Examples: `feat/array-splice-function`, `fix/float-coercion-negative-zero`, `test/phpt-match-expressions`.
97+
98+
### Commit Messages
99+
100+
Use the imperative mood with a type prefix:
101+
102+
- `feat: implement array_splice() with all overloads`
103+
- `fix: correct float-to-int coercion for negative zero`
104+
- `test: add PHPT-derived tests for match expressions`
105+
106+
### What to Include in a PR
107+
108+
1. **Failing tests first.** Every PR should include tests that demonstrate the behavior being added or fixed.
109+
2. **Minimal implementation.** Only the code necessary to make the tests pass.
110+
3. **All checks passing.** `cargo test`, `cargo fmt --all -- --check`, and `cargo clippy` must all succeed.
111+
4. **A clear description** of what the PR does and which PHP behavior it targets.
112+
113+
## Deriving Tests from PHP Source
114+
115+
The reference PHP test suite lives at `php-src/tests/` and `php-src/ext/*/tests/`. Tests use the `.phpt` format:
116+
117+
```
118+
--TEST--
119+
Description of the test
120+
--FILE--
121+
<?php
122+
echo 1 + 2;
123+
?>
124+
--EXPECT--
125+
3
126+
```
127+
128+
Key sections: `--TEST--` (description), `--FILE--` (PHP code), `--EXPECT--` (exact output), `--EXPECTF--` (output with format specifiers like `%s`, `%d`), `--SKIPIF--` (skip condition), `--INI--` (settings), `--ENV--`, `--ARGS--`.
129+
130+
To translate a `.phpt` file into a Rust test: write a test that compiles and executes the same PHP code and asserts the same output. Place unit tests in `#[cfg(test)]` modules within the relevant crate.
131+
132+
When behavior is ambiguous, run the PHP code against the reference interpreter (`php-src/sapi/cli/php`) and match the output exactly.
133+
134+
## Extension Development
135+
136+
Each PHP extension is its own crate under `crates/php-rs-ext-*/` (56 total). Every extension implements the `PhpExtension` trait with four lifecycle hooks:
137+
138+
- `module_init` -- called once when the module is loaded
139+
- `module_shutdown` -- called once when the module is unloaded
140+
- `request_init` -- called at the start of each request
141+
- `request_shutdown` -- called at the end of each request
142+
143+
### Adding a New Extension
144+
145+
1. Create a new crate: `crates/php-rs-ext-<name>/`
146+
2. Implement the `PhpExtension` trait and register all functions
147+
3. Write tests derived from `php-src/ext/<name>/tests/`
148+
4. Wire the extension into the SAPI crates so it loads at startup
149+
150+
All extensions must be **pure Rust** -- no C FFI bindings to existing PHP extensions. Do not implement deprecated PHP features (e.g., `mysql_*` functions, `each()`).
151+
152+
## Architecture Overview
153+
154+
The interpreter follows the same layered architecture as the official PHP implementation:
155+
156+
```
157+
Source Code --> Lexer --> Parser --> Compiler --> VM --> Output
158+
tokens AST opcodes execute
159+
```
160+
161+
### Crate Structure
162+
163+
| Layer | Crate | Purpose |
164+
|-------|-------|---------|
165+
| Types | `php-rs-types` | `ZVal` (16-byte tagged union), `ZString`, `ZArray`, `ZObject` |
166+
| Lexer | `php-rs-lexer` | Hand-written tokenizer (re2c-equivalent) |
167+
| Parser | `php-rs-parser` | Recursive descent parser producing AST |
168+
| Compiler | `php-rs-compiler` | AST to opcode array compilation |
169+
| VM | `php-rs-vm` | Virtual machine executor with 212 opcodes |
170+
| GC | `php-rs-gc` | Garbage collector with cycle detection |
171+
| Runtime | `php-rs-runtime` | INI system, output buffering, error handling, streams |
172+
| Extensions | `php-rs-ext-*` | One crate per extension (standard, json, pcre, curl, etc.) |
173+
| SAPIs | `php-rs-sapi-*` | Server API bindings (CLI, FPM, Embed, WASM) |
174+
175+
### Key Reference Files in php-src/
176+
177+
| What | Where |
178+
|------|-------|
179+
| Type system (zval layout) | `php-src/Zend/zend_types.h` |
180+
| VM opcodes (212) | `php-src/Zend/zend_vm_opcodes.h` |
181+
| VM handler definitions | `php-src/Zend/zend_vm_def.h` |
182+
| Compiler (AST to opcodes) | `php-src/Zend/zend_compile.c` |
183+
| Parser grammar | `php-src/Zend/zend_language_parser.y` |
184+
| Lexer definition | `php-src/Zend/zend_language_scanner.l` |
185+
| Standard library functions | `php-src/ext/standard/basic_functions.c` |
186+
187+
## Reporting Issues
188+
189+
### Bug Reports
190+
191+
Please include: (1) the PHP code that produces incorrect output, (2) expected output from the reference PHP 8.6 interpreter, (3) actual output from php.rs, (4) your Rust toolchain version (`rustc --version`), and (5) your OS and platform.
192+
193+
### Feature Requests
194+
195+
Feature requests are welcome. If requesting support for a specific PHP function or behavior, include a link to the PHP documentation and, if possible, the corresponding `.phpt` test files from `php-src/`.
196+
197+
## License
198+
199+
By contributing to php.rs, you agree that your contributions will be licensed under the [MIT License](LICENSE).

0 commit comments

Comments
 (0)