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
39 changes: 39 additions & 0 deletions bin/cgi-aot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
* CGI/1.1 wrapper for phpc-built AOT binaries (issue #665).
*
* nginx/apache set REQUEST_METHOD, QUERY_STRING, CONTENT_LENGTH, etc.; this reads
* the POST body from stdin, sets REQUEST_BODY, execs the native binary, and prints
* its CGI stdout (Status + headers + body).
*
* Usage:
* php bin/cgi-aot.php /path/to/aot-binary
* PHPC_DEPLOY_ROOT=/var/www/myapp php bin/cgi-aot.php
* phpc cgi /path/to/aot-binary
*/

require __DIR__.'/../src/tokenizer-compat.php';
require __DIR__.'/../src/yay-php8-compat.php';
require __DIR__.'/../src/llvm-env.php';
require __DIR__.'/../vendor/autoload.php';

use PHPCompiler\Web\CgiAotDriver;
use PHPCompiler\Web\DevServer;

$explicit = $argv[1] ?? null;
$deployRoot = getenv('PHPC_DEPLOY_ROOT');
$deployRoot = false !== $deployRoot ? $deployRoot : null;

try {
$binary = CgiAotDriver::resolveBinary($explicit, $deployRoot);
CgiAotDriver::run($binary, $deployRoot);
} catch (\Throwable $e) {
DevServer::logException($e);
$body = DevServer::formatExceptionBody($e);
fwrite(STDOUT, \PHPCompiler\Web\CgiDriver::formatResponse(500, 'text/plain', $body));
exit(0);
}
116 changes: 116 additions & 0 deletions bin/cgi-aot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# CGI/1.1 wrapper for phpc-built AOT binaries (issue #665). No PHP required at runtime.
#
# Usage:
# ./cgi-aot.sh /path/to/aot-binary
# PHPC_DEPLOY_ROOT=/var/www/myapp ./cgi-aot.sh
#
# nginx example (dist layout from phpc deploy):
# ScriptAlias /app /var/www/myapp/cgi-wrapper
set -euo pipefail

MAX_BODY=8388608
BODY_FILE=""
OUTFILE=""

usage() {
echo "Usage: $(basename "$0") <aot-binary>" >&2
echo " or: PHPC_DEPLOY_ROOT=/path/to/dist $(basename "$0")" >&2
exit 1
}

resolve_binary() {
if [[ $# -ge 1 && -n "${1:-}" ]]; then
if [[ ! -f "$1" ]]; then
echo "cgi-aot: binary not found: $1" >&2
exit 1
fi
printf '%s\n' "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
return 0
fi

local root="${PHPC_DEPLOY_ROOT:-}"
if [[ -z "$root" ]]; then
usage
fi
root="$(cd "$root" && pwd)"

if [[ -f "${root}/bin/app" ]]; then
printf '%s\n' "${root}/bin/app"
return 0
fi

echo "cgi-aot: no bin/app under PHPC_DEPLOY_ROOT=${root}" >&2
exit 1
}

ingest_stdin_body() {
local len="${CONTENT_LENGTH:-}"
if [[ -z "$len" || "$len" == "0" ]]; then
return 0
fi
if ! [[ "$len" =~ ^[0-9]+$ ]]; then
echo "cgi-aot: invalid CONTENT_LENGTH" >&2
exit 1
fi
if [[ "$len" -gt "$MAX_BODY" ]]; then
echo "cgi-aot: CONTENT_LENGTH exceeds limit" >&2
exit 1
fi

BODY_FILE="$(mktemp)"
export REQUEST_BODY_FILE="$BODY_FILE"
dd if=/dev/stdin of="$BODY_FILE" bs=1 count="$len" status=none 2>/dev/null || {
echo "cgi-aot: could not read request body" >&2
exit 1
}
if [[ "${REQUEST_METHOD:-}" != "POST" ]]; then
export REQUEST_METHOD=POST
fi
}

BINARY="$(resolve_binary "${1:-}")"
if [[ ! -x "$BINARY" ]]; then
chmod +x "$BINARY" 2>/dev/null || true
fi
if [[ ! -x "$BINARY" ]]; then
echo "cgi-aot: binary is not executable: $BINARY" >&2
exit 1
fi

if [[ -z "${PHPC_DEPLOY_ROOT:-}" ]]; then
bin_dir="$(dirname "$BINARY")"
if [[ "$(basename "$bin_dir")" == "bin" ]]; then
export PHPC_DEPLOY_ROOT="$(cd "$bin_dir/.." && pwd)"
fi
fi

ingest_stdin_body

OUTFILE="$(mktemp)"
cleanup() {
if [[ -n "${OUTFILE}" && -f "${OUTFILE}" ]]; then
rm -f "${OUTFILE}"
fi
if [[ -n "${BODY_FILE}" && -f "${BODY_FILE}" ]]; then
rm -f "${BODY_FILE}"
fi
}
trap cleanup EXIT

"$BINARY" >"$OUTFILE" 2>/dev/null || true
if [[ ! -s "$OUTFILE" ]]; then
echo "cgi-aot: binary produced no output" >&2
exit 1
fi

if grep -qi '^Status:' "$OUTFILE"; then
cat "$OUTFILE"
exit 0
fi

printf 'Status: 200 OK\r\n'
# AOT binaries emit a single CRLF between headers and body; CGI expects CRLF CRLF.
raw="$(<"$OUTFILE")"
printf '%s' "${raw/$'\r\n<'/$'\r\n\r\n<'}"
exit 0
14 changes: 14 additions & 0 deletions bin/phpc.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* phpc build [-o outfile] entry.php
* phpc build --project [dir] [--dry-run] AOT compile from phpc.json entry/binary
* phpc deploy [dir] -o <dist> [--from-build] Bundle binary, public/, assets/, phpc.json
* phpc cgi [binary] CGI wrapper for AOT binary (issue #665)
* phpc lint [-r 'code'] [--json] entry.php
* phpc lint --project <entry.php> [--json]
* phpc lint --all <dir-or-file> [--json]
Expand Down Expand Up @@ -44,6 +45,8 @@
--verbose On failure, keep full LLVM stderr (default adds #568 trailer)
phpc deploy [dir] -o <dist> Package AOT binary + manifest trees into dist/
--from-build Require existing binary (skip phpc build --project)
phpc cgi [binary] Run AOT binary under CGI env (stdin → REQUEST_BODY)
PHPC_DEPLOY_ROOT=<dist> Resolve bin/app from deploy bundle when binary omitted
phpc lint [-r 'code'] [--json] <entry.php> Report unsupported syntax (line-accurate)
phpc lint --project <entry.php> [--json] Entry + literal include/require chain
phpc lint --all <dir-or-file> [--json] All .php under a tree (aggregated)
Expand Down Expand Up @@ -90,6 +93,17 @@
require $repoRoot.'/vendor/autoload.php';
exit(deployFromProject($repoRoot, phpCommand(), $args));

case 'cgi':
if (!is_file($repoRoot.'/vendor/autoload.php')) {
fwrite(STDERR, "phpc cgi: run composer install first\n");
exit(1);
}
$cgiArgs = [];
while ([] !== $args) {
$cgiArgs[] = array_shift($args);
}
exit(runProcess(array_merge($php, array_merge([$repoRoot.'/bin/cgi-aot.php'], $cgiArgs)), $repoRoot));

case 'build':
if ([] !== $args && '--project' === $args[0]) {
array_shift($args);
Expand Down
38 changes: 22 additions & 16 deletions docs/bootstrap-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Regenerate: `php script/bootstrap-inventory.php`

| Metric | Count |
|--------|------:|
| PHP files on vm.php path | 336 |
| PHP files on vm.php path | 337 |
| Source constructs flagged (blockers) | 10 |
| Source constructs flagged (warnings) | 907 |
| Source constructs flagged (warnings) | 908 |

## Compiler CFG gaps (`lib/Compiler.php`)

Expand Down Expand Up @@ -345,6 +345,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `lib/VM/Refcount.php` | 0 | 1 |
| `lib/VM/TypeCheck.php` | 0 | 1 |
| `lib/VM/Variable.php` | 0 | 4 |
| `lib/Web/CgiAotDriver.php` | 0 | 1 |
| `lib/Web/CgiDriver.php` | 0 | 2 |
| `lib/Web/ConstStringFolder.php` | 0 | 1 |
| `lib/Web/DeployRoot.php` | 0 | 1 |
Expand Down Expand Up @@ -1863,14 +1864,14 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new OpCode (line 731)
- new Variable (line 920)
- new Variable (line 929)
- new Variable (line 1118)
- new Variable (line 1376)
- new Operand\Literal (line 1474)
- new Operand\Literal (line 1478)
- new Operand\Literal (line 1482)
- new Variable (line 1486)
- new Variable (line 1506)
- 20 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- new Variable (line 1140)
- new Variable (line 1391)
- new Operand\Literal (line 1489)
- new Operand\Literal (line 1493)
- new Operand\Literal (line 1497)
- new Variable (line 1501)
- new Variable (line 1521)
- 21 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/Analyzer.php`

Expand Down Expand Up @@ -2206,9 +2207,9 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:

**Warnings** (review for bootstrap subset):
- new Variable (line 22)
- new Variable (line 33)
- new Variable (line 40)
- new Variable (line 62)
- new Variable (line 36)
- new Variable (line 43)
- new Variable (line 65)
- 2 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/JitStringCompare.php`
Expand All @@ -2219,7 +2220,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `lib/JIT/JitValueBox.php`

**Warnings** (review for bootstrap subset):
- 5 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- 6 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/JitValueCompare.php`

Expand Down Expand Up @@ -2510,6 +2511,11 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new HashTable (line 106)
- 39 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/Web/CgiAotDriver.php`

**Warnings** (review for bootstrap subset):
- 3 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/Web/CgiDriver.php`

**Warnings** (review for bootstrap subset):
Expand Down Expand Up @@ -2556,8 +2562,8 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
### `lib/Web/ProjectDeploy.php`

**Warnings** (review for bootstrap subset):
- new RecursiveIteratorIterator (line 205)
- new RecursiveDirectoryIterator (line 206)
- new RecursiveIteratorIterator (line 220)
- new RecursiveDirectoryIterator (line 221)
- 6 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/Web/ProjectManifest.php`
Expand Down
5 changes: 3 additions & 2 deletions docs/bootstrap-profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@
"lib/VM/ScriptExit.php",
"lib/VM/TypeCheck.php",
"lib/VM/Variable.php",
"lib/Web/CgiAotDriver.php",
"lib/Web/CgiDriver.php",
"lib/Web/ConstStringFolder.php",
"lib/Web/DeployRoot.php",
Expand Down Expand Up @@ -401,9 +402,9 @@
"test/bootstrap-aot/lib_opcode/main.php"
],
"totals": {
"inventory_files": 336,
"inventory_files": 337,
"excluded": 2,
"eligible": 334,
"eligible": 335,
"aot_lint_targets": 15,
"aot_link_targets": 13,
"aot_link_lib_targets": 1
Expand Down
Loading