Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
725b16e
feat(matrix): add asFloat() conversion to a float matrix
claude Aug 13, 2026
9db027c
test(tests): cover asFloat() conversion to a float matrix
claude Aug 13, 2026
8ccdb35
feat(backend): extract pure-PHP maths into PhpBackend behind a Backen…
claude Aug 13, 2026
aa9117e
test(tests): cover backend selection, registration and env-var routing
claude Aug 13, 2026
713a609
feat(backend): add OpenBLAS driver with dgemm/daxpy/dscal over FFI
claude Aug 13, 2026
6ffb579
test(tests): cover the blas backend behaviours and php-parity
claude Aug 13, 2026
c607eee
feat(backend): add CLBlast/OpenCL GPU driver with selectable device type
claude Aug 13, 2026
9e001a4
test(tests): cover the clblast backend behaviours and php-parity
claude Aug 13, 2026
fed7f29
ci: install acceleration libraries and run backend-pinned suites
claude Aug 13, 2026
b68666a
feat(backend): add benchmark CLI comparing drivers
claude Aug 13, 2026
4cc7cfb
docs: document acceleration drivers, benchmarks and backend conventions
claude Aug 13, 2026
5373e1b
style: call global functions unqualified instead of importing them
claude Aug 14, 2026
89dd925
feat(matrix)!: store matrices as native float64 buffers
claude Aug 14, 2026
97f81c3
ci: matrix the gpu job over both PHP minors and run the whole suite
claude Aug 14, 2026
0871415
docs: rewrite acceleration guide for native float64 buffers
claude Aug 14, 2026
a3ddb4b
Merge branch 'master' into claude/matrix-php-ml-acceleration-crv7cc
claude Oct 19, 2026
9cc9618
fix(backend): allocate float64 buffers from an FFI scope, not statically
claude Oct 19, 2026
84ae238
feat(backend): make the php driver array-native with storage followin…
claude Oct 19, 2026
f9c698c
docs: document storage following the driver, with fresh benchmarks
claude Oct 19, 2026
3048dde
docs: align the phpt-author agent with float64 cells
claude Oct 19, 2026
b50399f
feat: track the z-engine 8.6 development line in parallel
claude Oct 19, 2026
d65d6a1
ci: run the suite on PHP 8.6
claude Oct 19, 2026
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
33 changes: 24 additions & 9 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,37 @@ Review against the checklist below, in this order of severity.

## 2. Version pins (blocking)

- `composer.json` must keep `"php": "^8.4"` and `"lisachenko/z-engine": "~8.4.2 || ~8.5.0"`
— one stable z-engine line per supported PHP minor, resolved by Composer against the
running PHP.
- `composer.json` must keep `"php": "^8.4"` and
`"lisachenko/z-engine": "~8.4.2 || ~8.5.0 || 8.6.x-dev"` — one z-engine line per
supported PHP minor, resolved by Composer against the running PHP: stable tags for 8.4
and 8.5, the `8.6.x-dev` development line for 8.6 until z-engine tags it (then it
becomes `~8.6.0` and `"prefer-stable": true` is dropped in the same change). The root
`"prefer-stable": true` is what keeps 8.4/8.5 on their tags while the dev flag is
present; a diff that removes it before then, or adds `"minimum-stability": "dev"`, is
rejected.
- Any diff that widens either constraint beyond the minors z-engine has definitions for
(`>=8.4`, a `^8.4` z-engine constraint that would span future lines), or bypasses
`Core::init()`'s version guard, is rejected outright. Offsets into engine structures are
minor-version specific; loosening the pin trades a clear error for memory corruption.
- CI must run on PHP 8.4 and 8.5 with `ffi.enable=1` and `opcache.jit=off`.
- CI must run on PHP 8.4, 8.5 and 8.6 with `ffi.enable=1` and `opcache.jit=off`.

## 3. PHPStan generics honesty (blocking)
## 3. float64 storage honesty (blocking)

`Matrix` is `@template-covariant T of int|float`.
`Matrix` has no generic parameter: cells are `float`, stored in the form of the driver
active when the matrix was built — a native `double[rows * columns]` buffer for a
`BufferBackendInterface` driver, a list of rows of PHP floats for an `ArrayBackendInterface`
one.

- Arithmetic that can widen the element type — division, exponentiation, and any mixed
int/float input — must be annotated as returning `Matrix<int|float>`, not `Matrix<T>`.
- Flag any annotation that claims to preserve `T` through an operation whose maths does not.
- Flag any reintroduced `Matrix<int>` / `Matrix<T>` annotation, or any signature claiming a
method returns integer cells. An integer literal in the constructor is input syntax only.
- Flag userland cast loops over cells. The constructor converting each cell while it
validates it is the only conversion there should be; an array driver that lets an int into
a result row (an accumulator seeded with `0`) breaks the float64 promise.
- Flag storage conversions on the hot path. Converting between buffer and rows is only for an
operand built under another driver, and only through `Float64Buffer::fromRows()` /
`toRows()` — never a PHP loop over the cells, never for operands that already match.
- Drivers must treat their operands as read-only and return freshly computed cells.
Returning an operand, or writing into one, is a blocking finding.
- New code must be clean at PHPStan level max; suppressing with `@phpstan-ignore` needs an
inline justification.

Expand Down
24 changes: 16 additions & 8 deletions .claude/agents/phpt-author.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ You write `.phpt` functional tests for `lisachenko/native-php-matrix`. Tests liv
## Before writing

Read `src/Matrix.php` to confirm the exact behaviour you are covering — which operand
orders `__doOperation` accepts, which exception type is thrown, and whether the result
elements come out as `int` or `float`. Read one or two existing tests in
orders `__doOperation` accepts and which exception type is thrown. Matrix cells are
float64 and nothing else, so every cell in a result dumps as `float(...)`, whichever
driver computed it and whatever literals went in. Read one or two existing tests in
`tests/Functional/` for the house style. Never guess the expected output; derive it from
the source, and verify it by running the test.

Expand Down Expand Up @@ -40,11 +41,11 @@ array(1) {
[0]=>
array(3) {
[0]=>
int(2)
float(2)
[1]=>
int(4)
float(4)
[2]=>
int(6)
float(6)
}
}
```
Expand Down Expand Up @@ -73,9 +74,16 @@ array(1) {
- **`--EXPECT--` over `--EXPECTREGEX--`.** Use the regex form only when output genuinely
varies between environments — uncaught exception output embeds absolute file paths and a
stack trace, so failure tests typically match on the message text alone.
- **Types matter.** `var_dump()` distinguishes `int(1)` from `float(1)`. PHP's `/` returns
an `int` only when the division is exact; `**` with a negative or fractional exponent
returns `float`. Get this right or the test is worthless.
- **Every cell expectation is a float.** `var_dump()` distinguishes `int(1)` from
`float(1)`, and matrix cells are always the latter — integer literals in the
constructor are input syntax, not a cell type. A test asserting `int(...)` for a
matrix cell is wrong by construction. Use integral-valued fixtures so the printed
form stays exact (`float(6)`, not `float(6.0000000000000004)`) under every driver.
- **A test whose output exposes the storage pins a backend.** Storage follows the
active driver (PHP rows under `php`, a native buffer under `blas`/`clblast`), so
anything that dumps the object itself — rather than `toArray()` — must pin
`NATIVE_PHP_MATRIX_BACKEND` in `--ENV--` or its expectation depends on which
libraries the machine happens to have.
- Keep matrices small — 1×3, 2×2, 3×1. The test documents a behaviour; it is not a
benchmark.

Expand Down
67 changes: 62 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,34 @@ on:
permissions:
contents: read

# z-engine tracks one PHP minor per release line; the "~8.4.2 || ~8.5.0" constraint
# lets Composer resolve the stable line matching each runner's PHP, so the suite
# runs on PHP 8.4 and 8.5 in parallel.
# z-engine tracks one PHP minor per release line; the "~8.4.2 || ~8.5.0 || 8.6.x-dev"
# constraint lets Composer resolve the line matching each runner's PHP, so the suite
# runs on PHP 8.4, 8.5 and 8.6 in parallel. 8.4 and 8.5 resolve stable tags; 8.6
# resolves z-engine's master (branch-aliased 8.6.x-dev) until z-engine tags 8.6. The
# 8.6 legs are set up exactly as z-engine's own CI sets up its 8.6 line (plain
# php-version '8.6', which setup-php serves pre-release builds for too) and, like
# there, they are required, not allowed to fail.
jobs:
tests:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
php: ['8.4', '8.5', '8.6']
# The suite fails on skipped tests, so every acceleration library is installed here: a skipped backend test
# means a broken environment rather than an absent GPU. PoCL provides the OpenCL device, on the CPU
env:
NATIVE_PHP_MATRIX_CL_DEVICE: cpu
steps:
- uses: actions/checkout@v7

- name: Install acceleration libraries
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libopenblas0 libclblast1 ocl-icd-libopencl1 pocl-opencl-icd

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
Expand All @@ -37,13 +51,56 @@ jobs:
- name: Run test suite
run: composer test

# Backend-pinned tests carry their own --ENV--, which overrides this one: what the second run really covers
# is every other test computing on the pure-PHP driver, with automatic routing switched off
- name: Run test suite on the pure PHP backend
run: composer test
env:
NATIVE_PHP_MATRIX_BACKEND: php

gpu-path:
name: GPU code path (CLBlast on PoCL, PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5', '8.6']
# The device code path gets its own red/green signal, on the CPU device PoCL provides, so a broken GPU driver
# is visible without reading through the rest of the suite. Now that matrix cells are float64 whichever driver
# computes them, pinning an accelerated backend no longer changes any expectation, so this job runs the entire
# suite on the device instead of only the driver's own tests
env:
NATIVE_PHP_MATRIX_BACKEND: clblast
NATIVE_PHP_MATRIX_CL_DEVICE: cpu
OPENBLAS_NUM_THREADS: '1'
steps:
- uses: actions/checkout@v7
Comment thread
lisachenko marked this conversation as resolved.
Resolved

- name: Install acceleration libraries
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libopenblas0 libclblast1 ocl-icd-libopencl1 pocl-opencl-icd

- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ffi
ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off
coverage: none

- uses: ramsey/composer-install@v4

- name: Run test suite on the clblast backend
run: composer test

static-analysis:
name: PHPStan (level max, PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.4', '8.5']
php: ['8.4', '8.5', '8.6']
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
Expand Down
11 changes: 9 additions & 2 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
HEADER;

$finder = PhpCsFixer\Finder::create()
->in([__DIR__ . '/src'])
->in([__DIR__ . '/src', __DIR__ . '/bench'])
->name('*.php')
->append([__FILE__, __DIR__ . '/bootstrap.php']);
->append([__FILE__, __DIR__ . '/bootstrap.php'])
// Test fixtures are ordinary PHP holding the shared backend stubs and SKIPIF probes; the ".inc" suffix only
// keeps them out of the PHPUnit suite, which collects ".phpt" files
->append(
PhpCsFixer\Finder::create()
->in([__DIR__ . '/tests/Functional/include'])
->name('*.inc'),
);

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
Expand Down
Loading
Loading