Skip to content

Latest commit

 

History

History
364 lines (278 loc) · 11.1 KB

QUICK_START.md

File metadata and controls

364 lines (278 loc) · 11.1 KB

Quick Start

5 分钟上手 TinyPHP 开发。


1. 跑通第一个测试

# 编译并运行
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

2. 调用自定义 C 函数

写一个 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

C 类型注解(可选)

借鉴 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*)

安全 API(防 UAF / double-free)

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

3. 多线程

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 多线程章节。


4. 异步与协程通信

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 异步与协程通信章节。


5. Android 构建

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

支持的 ABI

-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。


6. 开发流程

改代码 → 跑相关测试 → 全量 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 跑基准

7. 代码约定

文件 职责
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 验证类型和值

8. 常见问题

Q: 编译报 macro used with too many args?

A: 宏参数含逗号(如 VAR_STRING((t_string){a,b}))会被预处理器误拆分。用临时变量:

t_string _tmp = {a, (int)strlen(a)};
VAR_STRING(_tmp);  // OK

Q: TCC / GCC / Clang 行为不一致?

A: compat.h 处理差异。TCC 不报隐式声明,GCC/Clang 报。用 -cc gcc 或 -cc clang 额外验证。

Q: #debug 怎么写?

#debug text      → 精确匹配
#debug           → 预期空行
#debug ~ text    → 近似值(时间/时区相关),不判错

详细架构见 CONTRIBUTING.md。