feat(compiler): add expected and unexpected branch prediction functions

- Implemented expected() and unexpected() compile-time functions for branch prediction hints
- Added C++ macro generation for EXPECTED(...) and UNEXPECTED(...) in compiled output
- Updated documentation to include new branch prediction functions
- Added comprehensive tests for branch prediction macro generation
- Modified compiler base to handle expected/unexpected function type detection
- Added parser support for branch prediction function calls with proper argument validation
- Included polyfill implementations for expected and unexpected functions
- Updated version number from 1086 to 1087
pull/34/head
韩天峰 1 month ago
parent 90b61fbc42
commit 576222998c
  1. 6
      docs/COMPILE_TIME_FUNCTIONS.md
  2. 2
      docs/README.md
  3. 12
      phpunit/code/branch-prediction.php
  4. 27
      phpunit/src/BranchPredictionTest.php
  5. 4
      src/CompilerBase.php
  6. 8
      src/Parser/FunctionCallTrait.php
  7. 10
      src/polyfills.php
  8. 39
      tests/compiler/control_flow/branch-prediction.phpt
  9. 2
      version.txt

@ -4,19 +4,22 @@
## 核心编译期函数 ## 核心编译期函数
当前核心全局编译期函数共 3 个。 当前核心全局编译期函数共 5 个。
| 名称 | 参数 | 作用 | 当前主要处理位置 | | 名称 | 参数 | 作用 | 当前主要处理位置 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `any($value)` | 1 个 | 将表达式降级为 `mixed/any`,阻止继续按静态 native/object 类型处理。 | 通用函数调用表达式入口。 | | `any($value)` | 1 个 | 将表达式降级为 `mixed/any`,阻止继续按静态 native/object 类型处理。 | 通用函数调用表达式入口。 |
| `refval($target)` | 1 个 | 显式把变量、数组元素或对象属性作为引用传给动态调用或无法静态识别引用参数的调用。 | 参数解析、动态调用、SSA/优化器引用逃逸分析。 | | `refval($target)` | 1 个 | 显式把变量、数组元素或对象属性作为引用传给动态调用或无法静态识别引用参数的调用。 | 参数解析、动态调用、SSA/优化器引用逃逸分析。 |
| `objval($value, ClassName::class 或 'ClassName')` | 2 个 | 告诉编译器 `$value` 是指定类对象,并生成 `php::toObject(..., target_ce)` 运行时兜底检查。 | 函数调用解析、对象类型推导。 | | `objval($value, ClassName::class 或 'ClassName')` | 2 个 | 告诉编译器 `$value` 是指定类对象,并生成 `php::toObject(..., target_ce)` 运行时兜底检查。 | 函数调用解析、对象类型推导。 |
| `expected($condition)` | 1 个 | 标记条件通常为真,生成 Zend `EXPECTED(...)` 分支预测宏。 | 通用函数调用表达式入口。 |
| `unexpected($condition)` | 1 个 | 标记条件通常为假,生成 Zend `UNEXPECTED(...)` 分支预测宏。 | 通用函数调用表达式入口。 |
约束: 约束:
- `refval()` 只接受变量、数组元素或对象属性。 - `refval()` 只接受变量、数组元素或对象属性。
- `objval()` 第二个参数必须是编译期可解析的类名字符串或 `ClassName::class` - `objval()` 第二个参数必须是编译期可解析的类名字符串或 `ClassName::class`
- `any()` 可在任意表达式位置使用,编译时直接展开其唯一参数,不生成运行时函数调用。 - `any()` 可在任意表达式位置使用,编译时直接展开其唯一参数,不生成运行时函数调用。
- `expected()` / `unexpected()` 只接受一个非展开参数,返回 bool;通常用于 `if`、`elseif` 和循环条件,不改变参数的求值次数及真假语义。
## 关键词方法 ## 关键词方法
@ -84,6 +87,7 @@
- `any()` 已统一在普通函数调用表达式入口处理;赋值、参数、返回值、数组元素和运算子表达式共用相同语义。 - `any()` 已统一在普通函数调用表达式入口处理;赋值、参数、返回值、数组元素和运算子表达式共用相同语义。
- `refval()` / `toRef()` 在参数解析和动态调用路径中特判较多,后续应统一为一个“引用包装表达式”解析入口。 - `refval()` / `toRef()` 在参数解析和动态调用路径中特判较多,后续应统一为一个“引用包装表达式”解析入口。
- `objval()` 当前通过函数调用解析和类型推导路径识别,整体较集中。 - `objval()` 当前通过函数调用解析和类型推导路径识别,整体较集中。
- `expected()` / `unexpected()` 在普通函数调用入口分别生成 `EXPECTED(...)` / `UNEXPECTED(...)`,不产生 PHP 运行时函数调用。
后续重构目标: 后续重构目标:

@ -9,7 +9,7 @@
- [编译器命令行](COMPILER_CLI.md):当前 CLI 参数和项目配置。 - [编译器命令行](COMPILER_CLI.md):当前 CLI 参数和项目配置。
- [编译模式](COMPILATION_MODES.md):binary、extension、library 模式。 - [编译模式](COMPILATION_MODES.md):binary、extension、library 模式。
- [快速入门](QUICKSTART.md):最小编译流程。 - [快速入门](QUICKSTART.md):最小编译流程。
- [编译期函数](COMPILE_TIME_FUNCTIONS.md):`any()`、`refval()`、`objval()` 和关键词方法。 - [编译期函数](COMPILE_TIME_FUNCTIONS.md):`any()`、`refval()`、`objval()`、`expected()`、`unexpected()` 和关键词方法。
- [原生类型](NATIVE_TYPES.md)、[高精度类型](HIGH_PRECISION_TYPES.md)、[Std 容器](STD_CONTAINERS.md)。 - [原生类型](NATIVE_TYPES.md)、[高精度类型](HIGH_PRECISION_TYPES.md)、[Std 容器](STD_CONTAINERS.md)。
- [通用与扩展方法](UNIVERSAL_METHODS.md)、[Generator](YIELD_GENERATOR.md)。 - [通用与扩展方法](UNIVERSAL_METHODS.md)、[Generator](YIELD_GENERATOR.md)。
- [类继承](CLASS_INHERITANCE.md)、[混合 C++/PHP](MIXED_CPP_PHP.md)。 - [类继承](CLASS_INHERITANCE.md)、[混合 C++/PHP](MIXED_CPP_PHP.md)。

@ -0,0 +1,12 @@
<?php
function phpunit_branch_prediction(bool $likely, mixed $unlikely): int
{
if (expected($likely)) {
return 1;
}
if (unexpected((bool) $unlikely)) {
return 2;
}
return 3;
}

@ -0,0 +1,27 @@
<?php
use PHPUnit\Framework\TestCase;
use TypePhp\CompilerTest;
final class BranchPredictionTest extends TestCase
{
public function testGeneratesExpectedAndUnexpectedMacros(): void
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
$file = __DIR__ . '/../code/branch-prediction.php';
$compiler->addFiles([$file]);
$compiler->prepareFile($file);
$cppFile = $compiler->convertFile($file);
$code = file_get_contents($cppFile);
$this->assertStringContainsString('if (static_cast<bool>(EXPECTED((likely))))', $code);
$this->assertStringContainsString(
'if (static_cast<bool>(UNEXPECTED((php::toBool(unlikely)))))',
$code,
);
$this->assertStringNotContainsString('php_expected(', $code);
$this->assertStringNotContainsString('php_unexpected(', $code);
}
}

@ -2488,6 +2488,7 @@ class CompilerBase implements PropertyAccessContext
case 'Expr_FuncCall': case 'Expr_FuncCall':
if ($this->isNameExpr($expr->name)) { if ($this->isNameExpr($expr->name)) {
$name = $this->parseIdentifier($expr->name); $name = $this->parseIdentifier($expr->name);
$globalName = ltrim($name, '\\');
// Math function optimization: propagate Big* return types // Math function optimization: propagate Big* return types
if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) && !empty($expr->args)) { if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) && !empty($expr->args)) {
$argType = $this->detectTypeOfExpr($expr->args[0]->value); $argType = $this->detectTypeOfExpr($expr->args[0]->value);
@ -2513,6 +2514,9 @@ class CompilerBase implements PropertyAccessContext
if (in_array($name, self::STREAM_FUNCTIONS)) { if (in_array($name, self::STREAM_FUNCTIONS)) {
return Type::STREAM; return Type::STREAM;
} }
if ($globalName === 'expected' || $globalName === 'unexpected') {
return Type::BOOL;
}
if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) {
return Type::OBJECT; return Type::OBJECT;
} }

@ -77,6 +77,7 @@ trait FunctionCallTrait
$name = ''; $name = '';
} elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') { } elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') {
$name = $this->parseIdentifier($expr->name); $name = $this->parseIdentifier($expr->name);
$globalName = ltrim($name, '\\');
if (in_array($name, Constants::UNSUPPORTED_FUNCTIONS)) { if (in_array($name, Constants::UNSUPPORTED_FUNCTIONS)) {
$this->fatalError($expr, 'Unsupported function: `' . $name . '`'); $this->fatalError($expr, 'Unsupported function: `' . $name . '`');
} }
@ -86,6 +87,13 @@ trait FunctionCallTrait
} }
return $this->parseExprAsValue($expr->args[0]->value); return $this->parseExprAsValue($expr->args[0]->value);
} }
if ($globalName === 'expected' || $globalName === 'unexpected') {
if (count($expr->args) !== 1 || $expr->args[0]->unpack) {
$this->fatalError($expr, "The {$globalName} function expects exactly one non-unpacked argument");
}
$condition = $this->parseExprAsValue($expr->args[0]->value);
return 'static_cast<bool>(' . strtoupper($globalName) . '((' . $condition . ')))';
}
if ($name === 'objval') { if ($name === 'objval') {
return $this->genObjvalCall($expr); return $this->genObjvalCall($expr);
} }

@ -103,6 +103,16 @@ function any(mixed $var): mixed
return $var; return $var;
} }
function expected(mixed $condition): bool
{
return (bool) $condition;
}
function unexpected(mixed $condition): bool
{
return (bool) $condition;
}
/** /**
* @throws Exception * @throws Exception
*/ */

@ -0,0 +1,39 @@
--TEST--
expected and unexpected provide branch prediction hints without changing condition semantics
--FILE--
<?php
function predicted_condition(int &$calls, bool $result): bool
{
$calls++;
return $result;
}
function main(): void
{
$calls = 0;
if (expected(predicted_condition($calls, true))) {
echo "expected\n";
}
if (unexpected(predicted_condition($calls, false))) {
echo "unexpected-true\n";
} else {
echo "unexpected-false\n";
}
if (\expected(condition: predicted_condition($calls, true))) {
echo "fully-qualified\n";
}
var_dump(expected(1), unexpected(0), $calls);
}
?>
--EXPECT--
expected
unexpected-false
fully-qualified
bool(true)
bool(false)
int(3)

@ -1 +1 @@
1086 1087

Loading…
Cancel
Save