5 分钟上手 TinyPHP 开发。
# 编译并运行
php tphp.php test/main/min.php
./min # Linux/macOS
.\min.exe # Windows
# 带 #debug 的测试(自动比对预期输出)
php tphp.php test/var/var.php --debug
# CI 批量测试
php .github/scripts/run_tests.php写一个 my_func.c:
#include <stdio.h>
int my_add(int a, int b) {
return a + b;
}PHP 中通过 C-> 直接调用:
// (<?php 前缀可选)
#debug 30
class Main {
public function main(): void {
// ⚠️ C-> 调用返回值赋给变量时必须显式声明类型(AOT 类型安全)
// 独立语句(无返回值使用)不需要声明,如 C->foo();
// 表达式上下文需用 cast 包装,如 php_int(C->foo())
int $r = C->my_add(c_int(10), c_int(20));
var_dump($r);
}
}编译时把 .c 一起带上:
php tphp.php main.php my_func.c --debug
# [YES] 30借鉴 vlang 的 C.Type 设计,函数参数和返回值可直接使用 C 类型,编译器自动映射:
#include "my_func.h"
function add(int $a, int $b): C.int { // 返回 C int
return C->my_add(c_int($a), c_int($b));
}
// C 结构体指针作为参数和返回值
function make_point(): C.Point* { return C->point_create(); }
function get_x(C.Point* $p): C.double { return C->point_get_x($p); }| C 类型注解 | 映射 |
|---|---|
C.int |
int |
C.int32 / C.int64 |
int32_t / int64_t |
C.uint32 / C.uint64 |
uint32_t / uint64_t |
C.float / C.double |
double |
C.char |
char |
C.bool |
bool |
C.void |
void |
C.void* / C.char* / C.int* / C.double* |
void* / char* / int* / double* |
C.XXX |
XXX(结构体值类型) |
C.XXX* |
XXX*(结构体指针,如 C.Point* → Point*) |
phpc 提供 4 个安全辅助函数,处理 C 指针生命周期边界问题:
// 1. defer 是首选清理方式(编译期展开到所有退出路径,零运行时开销)
// 适用:C 库返回的 T* 指针、fopen 句柄、phpc_arr_str 等
C.void* $buf = C->malloc(64);
defer C->free($buf); // 函数退出自动释放
C.FILE* $f = C->fopen(c_str("x.txt"), c_str("rb"));
defer C->fclose($f); // 所有 return 路径自动关闭
// 2. phpc_arr_int/dbl 自动注册到运行时,无需手动释放
// (仅循环内为避免内存堆积才需手动 phpc_free)
C.int32_t* $data = phpc_arr_int([1, 2, 3]); // 自动注册,程序结束/异常自动释放
int $result = C->sum_ints($data, c_int(3)); // C-> 调用必须显式声明返回类型
// phpc_arr_str 不自动注册,需用 defer 释放
C.char** $strs = phpc_arr_str(["a", "b"]);
defer phpc_free_str_arr($strs, c_int(2));
// 3. phpc_free 显式释放(释放后自动置零变量,防 use-after-free)
phpc_free($data); // $data 自动为 null
// 4. phpc_assert_ptr 断言指针非 NULL,失败时抛 tp_throw 异常(可 try-catch)
C.void* $ptr = C->maybe_returns_null();
try {
phpc_assert_ptr($ptr, "ptr_name");
// 安全使用 $ptr
} catch (\Exception $e) {
// 捕获 NULL 指针错误
}
// 5. phpc_obj_steal 标记对象为"已分离"(refcount=-1),防 tp_obj_release double-free
C.Point* $p = C->point_create(1.0, 2.0);
phpc_obj_steal($p); // 标记分离
C->point_free($p); // C 库释放,TinyPHP GC 不会再次释放
// 6. phpc_env_pin / phpc_env_unpin 钉住闭包 env,防异步回调 UAF
$fn = function(int $x) use ($captured): int { return $x * $captured; };
$env = phpc_env_pin($fn); // 钉住 env,防闭包出作用域被回收
// ... C 库异步回调安全使用 $env ...
phpc_env_unpin($env); // 用完释放| API | 作用 | 防护对象 |
|---|---|---|
defer C->free($p) / defer C->fclose($f) |
编译期展开到退出路径 | 资源泄漏(首选) |
defer phpc_free_str_arr($a, $n) |
字符串数组释放 | 资源泄漏 |
phpc_free($var) |
显式释放 + 自动置零 | use-after-free |
phpc_assert_ptr($ptr, $name) |
NULL 断言 → tp_throw | NULL 解引用 |
phpc_obj_steal($obj) |
refcount=-1 | double-free |
phpc_env_pin($cb) / phpc_env_unpin($env) |
钉住闭包 env | 异步回调 UAF |
TinyPHP 内置 Thread/Mutex/CondVar/WaitGroup 四个 OOP 线程类(基于 tinycthread)。采用 Thread-Local 运行时策略,每线程独立内存池,无锁竞争。
<?php
#debug ret=42
#debug sync=1
class Main {
public function main(): void {
// Thread + join
$t = new Thread(function(): int { return 42; });
$t->start();
echo "ret=" . $t->join() . "\n"; // 42
// WaitGroup 跨线程同步
$wg = new WaitGroup();
$wg->add(1);
$t2 = new Thread(function() use ($wg): int {
$wg->done();
return 0;
});
$t2->start();
$wg->wait();
$t2->join();
echo "sync=1\n";
}
}php tphp.php thread_demo.php --debug
# [YES] ret=42
# [YES] sync=1| 类 | 常用方法 |
|---|---|
Thread |
start() / join() / detach() + 静态 yield() / sleep($s) / id() |
Mutex |
lock() / tryLock() / unlock()(构造参数 recursive=true 用递归锁) |
CondVar |
wait(Mutex $m) / signal() / broadcast() |
WaitGroup |
add(int $n) / done() / wait() |
闭包须返回
int作为线程退出码。详见 FUNCTIONS.md 多线程章节。
TinyPHP 内置 Channel/Future 两个 OOP 异步原语 + chan_select 多路复用函数(参考 vlang CSP 模型)。基于 tinycthread 的 mutex + condvar 实现,push/pop/await 阻塞前自旋 750 次减少 syscall,Channel 使用环形缓冲区零 malloc。
<?php
#debug basic=42
#debug cross=hello
#debug then=20
#debug ready_idx=1
class Main {
public function main(): void {
// Channel 基本收发
$ch = new Channel(8);
$ch->push(42);
echo "basic=" . $ch->pop() . "\n"; // 42
// Channel 跨线程通信
$ch2 = new Channel(4);
$producer = new Thread(function() use ($ch2): int {
$ch2->push("hello");
return 0;
});
$producer->start();
echo "cross=" . $ch2->pop() . "\n"; // hello
$producer->join();
// Future 链式回调
$f = Future::create();
$f->resolve(10);
$doubled = $f->then(fn(mixed $x): mixed => $x * 2);
echo "then=" . $doubled->await() . "\n"; // 20
// chan_select 多路复用
$ch1 = new Channel(4);
$ch2s = new Channel(4);
$ch2s->push("ready");
$idx = chan_select([$ch1, $ch2s], 100); // 1
echo "ready_idx=" . $idx . "\n";
}
}php tphp.php async_demo.php --debug
# [YES] basic=42
# [YES] cross=hello
# [YES] then=20
# [YES] ready_idx=1| 类 / 函数 | 常用方法 |
|---|---|
Channel |
push($v) / pop() / tryPush($v) / tryPop() / close() / isClosed() / length() / capacity() |
Future |
静态 create() / resolve($v) / reject($err) / await() / isReady() / then($cb) / catch($cb) + 静态 all($futures) / race($futures) |
chan_select |
chan_select(array $channels, int $timeout_ms = -1): int(就绪索引 / -1 超时 / -2 全关闭) |
异常:push 到已关闭通道抛 ChannelClosedException;await 被 reject 的 Future 抛 FutureRejectedException。两者都继承 Exception,可被 try-catch 捕获。
内存安全:即使忘记
close(),Channel dtor 也会释放剩余元素;Future dtor 释放 result/error。详见 FUNCTIONS.md 异步与协程通信章节。
TinyPHP 支持将 PHP UI 应用编译为 Android APK。使用 NDK Clang 工具链交叉编译为 libtphp.so,通过 Gradle 打包为 APK。
| 环境变量 | 说明 |
|---|---|
ANDROID_NDK |
NDK 根目录(必需,如 C:\Android\ndk\27.0.12077973) |
JAVA_HOME |
JDK 17/21 路径(APK 打包必需,Java 24+ 不兼容 Gradle 8.9) |
ANDROID_HOME |
Android SDK 路径(APK 打包必需) |
TPHP_ANDROID_API |
目标 API 级别(默认 24 = Android 7.0) |
# 默认编译全部 4 种 ABI,生成 <baseName>-debug.apk
php tphp.php test/ui/ui_basic.php -os android
# 指定输出名(生成 myapp-debug.apk)
php tphp.php test/ui/ui_basic.php -os android -o myapp
# 仅编译单 ABI(加速本地测试)
php tphp.php test/ui/ui_basic.php -os android -arch x86_64 # 模拟器
php tphp.php test/ui/ui_basic.php -os android -arch aarch64 # 真机| 产物 | 路径 | 说明 |
|---|---|---|
libtphp.so |
build/android/jniLibs/<abi>/ |
各 ABI 共享库(供 Gradle 打包) |
<baseName>-debug.apk |
当前工作目录 | 最终 APK |
-arch 值 |
ABI | 典型场景 |
|---|---|---|
aarch64 |
arm64-v8a | 64 位真机 |
x86_64 |
x86_64 | 64 位模拟器 |
armv7a |
armeabi-v7a | 32 位真机 |
i686 |
x86 | 32 位模拟器 |
仅设置
ANDROID_NDK而不设置JAVA_HOME/ANDROID_HOME时,跳过 APK 打包但仍生成libtphp.so。详见 ext/ui/android/README.md。
改代码 → 跑相关测试 → 全量 CI → PR
↓ ↓
php tphp.php php .github/scripts/run_tests.php
test/xxx.php --debug
| 命令 | 用途 |
|---|---|
php tphp.php test/foo.php |
编译单个测试 |
php tphp.php test/foo.php --debug |
编译 + 运行 + 比对输出 |
php tphp.php test/foo.php -cc gcc |
指定编译器 |
php .github/scripts/run_tests.php |
跑全部测试 |
php bench/run_bench.php gcc |
跑基准 |
| 文件 | 职责 |
|---|---|
src/Lexer.php |
词法分析 → Token |
src/Parser.php |
语法分析 → AST |
src/CodeGenerator.php |
AST → C 代码 |
include/ + include/std/*.h |
C 运行时(全部 static inline, 8个分类子文件) |
tphp.php |
CLI 入口、路径安全、合并 AST |
- C 函数统一前缀
tphp_fn_ - 跨编译器兼容用
compat.h,不要直接#include <math.h> - 测试用
var_dump+#debug验证类型和值
Q: 编译报 macro used with too many args?
A: 宏参数含逗号(如 VAR_STRING((t_string){a,b}))会被预处理器误拆分。用临时变量:
t_string _tmp = {a, (int)strlen(a)};
VAR_STRING(_tmp); // OKQ: TCC / GCC / Clang 行为不一致?
A: compat.h 处理差异。TCC 不报隐式声明,GCC/Clang 报。用 -cc gcc 或 -cc clang 额外验证。
Q: #debug 怎么写?
#debug text → 精确匹配
#debug → 预期空行
#debug ~ text → 近似值(时间/时区相关),不判错
详细架构见 CONTRIBUTING.md。