feat(aot): 添加对UnsafePtr类型和相关函数的支持

- 在ArgInfo类中添加unsafePtr属性
- 添加UnsafePtr类型到编译器类型映射
- 实现std::unsafe_ptr()和std::unsafe_cast()函数的解析和处理逻辑
- 添加对UnsafePtr类型的变量跟踪和验证机制
- 支持通过unsafe_cast进行标准容器的不安全转换
- 添加相应的单元测试验证功能正确性
pull/1/head
韩天峰 4 months ago
parent ee240aed25
commit 79bc9d4e69
  1. 8
      phpunit/src/AssignTest.php
  2. 1
      src/Php/ArgInfo.php
  3. 67
      src/Php/CompilerBase.php
  4. 5
      src/Php/Context/FunctionContext.php
  5. 66
      src/Php/Parser/StdContainerParser.php
  6. 10
      src/polyfills.php
  7. 25
      tests/aot/std-array/007.phpt
  8. 25
      tests/aot/std-map/005.phpt
  9. 25
      tests/aot/std-unordered-map/005.phpt
  10. 32
      tests/aot/std-vector/004.phpt
  11. 32
      tests/aot/std-vector/005.phpt
  12. 25
      tests/aot/std-vector/006.phpt

@ -19,4 +19,12 @@ class AssignTest extends \BaseTest
'std-container-static-class-mismatch.php'
);
}
public function testStdUnsafeCastRequiresUnsafePtr()
{
$this->exec(
'std::unsafe_cast() expects second argument to be declared as UnsafePtr',
'std-unsafe-cast-requires-unsafe-ptr.php'
);
}
}

@ -21,4 +21,5 @@ class ArgInfo
public bool $variadic = false;
public bool $nullable = false;
public bool $property = false;
public bool $unsafePtr = false;
}

@ -144,6 +144,8 @@ class CompilerBase extends \PhpAot\Core\Translator
'callable' => self::TYPE_VAR,
// iterable 类型,可以是数组或者对象
'iterable' => self::TYPE_VAR,
// 编译器符号类型,C++ 层仍使用 php::Var 承载 null zval 中的 value.ptr
'UnsafePtr' => self::TYPE_VAR,
];
protected array $globalHeaders = [
'phpx.h',
@ -1044,6 +1046,9 @@ class CompilerBase extends \PhpAot\Core\Translator
}
foreach ($this->functionDef->argInfoList as $argInfo) {
$this->addArgument($argInfo->name, $argInfo->type);
if ($argInfo->unsafePtr) {
$this->addUnsafePtr($argInfo->name);
}
if ($argInfo->class
and !$this->isAbstractClass($argInfo->class)
and !$this->hasInterface($argInfo->class)
@ -1532,7 +1537,15 @@ class CompilerBase extends \PhpAot\Core\Translator
} elseif ($this->isStaticCall($right) and $this->isNameExpr($right->class) and $this->isIdExpr($right->name)) {
$class = $this->parseIdentifier($right->class);
if ($class === 'std') {
if (in_array($right->name->toString(), ['array', 'vector', 'map', 'unordered_map'], true)) {
if ($right->name->toString() === 'unsafe_cast') {
if ($this->hasVar($var)) {
$this->fatalError($left, "Cannot re-assign `\${$var}` to std::unsafe_cast()");
}
if ($this->context->scopeLevel > 1) {
$this->fatalError($left, 'Must use std::unsafe_cast() in the top-level scope of the function');
}
return $this->parseStdUnsafeCastAssign($var, $right);
} elseif (in_array($right->name->toString(), ['array', 'vector', 'map', 'unordered_map'], true)) {
if ($this->hasVar($var)) {
$this->fatalError($left, "Cannot re-assign `\${$var}` to std::{$right->name->toString()}");
}
@ -1558,6 +1571,9 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $right->getAttribute('nativeType'));
}
if ($right->getAttribute('unsafePtr')) {
$this->addUnsafePtr($var);
}
return $var . ' = ' . $valueExpr;
}
}
@ -1876,6 +1892,16 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->context->localVars[$name] = $type;
}
protected function addUnsafePtr(string $name): void
{
$this->context->unsafePtrs[$name] = true;
}
protected function isUnsafePtr(string $name): bool
{
return isset($this->context->unsafePtrs[$name]);
}
protected function addTmpVar(string $type): string
{
$var = $this->genTmpVarName();
@ -2283,6 +2309,10 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($param->byRef) {
return self::TYPE_REF;
}
if ($this->isUnsafePtrTypeDecl($param->type)) {
$argInfo->unsafePtr = true;
return self::TYPE_VAR;
}
$class = '';
$type = $this->parseTypeDecl($param->type, self::DECL_TYPE_OF_PARAM, $class);
if ($class) {
@ -5380,11 +5410,22 @@ class CompilerBase extends \PhpAot\Core\Translator
$expr->setAttribute('nativeType', $type);
$valueExpr = $this->parseExpr($expr->args[0]->value);
return $this->convertExprFromType($type, $valueExpr);
} elseif ($func === 'unsafe_ptr') {
$expr->setAttribute('nativeType', self::TYPE_VAR);
return $this->parseStdUnsafePtr($expr);
} else {
$this->fatalError($expr, 'Unknown std method: ' . $func);
}
}
protected function isUnsafePtrTypeDecl(?NodeAbstract $type): bool
{
return $type !== null
and !$type instanceof UnionType
and !$type instanceof NullableType
and $this->parseIdentifier($type) === 'UnsafePtr';
}
protected function parseParentMethodCall(Expr\StaticCall $expr): string
{
$methodStr = $this->classDef->name . '::' . $this->parseIdentifier($expr->name);
@ -5426,7 +5467,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$code .= $this->getIndent();
if ($type === self::TYPE_STD_ARRAY) {
$info = $this->context->stdArrays[$name];
if ($info['bytes'] > self::MAX_BYTES_IN_STACK) {
if (isset($info['unsafePtr'])) {
$code .= 'auto &' . $name . ' = *reinterpret_cast<' . $info['decl'] . '*>(Z_PTR_P(' . $info['unsafePtr'] . '.ptr()));';
} elseif ($info['bytes'] > self::MAX_BYTES_IN_STACK) {
$code .= "auto {$name}_unique_ptr = std::make_unique<{$info['decl']}>();\n";
$code .= $this->getIndent() . ' auto &' . $name . ' = *' . $name . '_unique_ptr;';
} else {
@ -5434,16 +5477,24 @@ class CompilerBase extends \PhpAot\Core\Translator
}
} elseif ($type === self::TYPE_STD_VECTOR) {
$info = $this->context->stdContainers[$name];
$code .= $info['decl'] . ' ' . $name;
if ($info['size'] !== null) {
$code .= '(' . $info['size'] . ')';
if (isset($info['unsafePtr'])) {
$code .= 'auto &' . $name . ' = *reinterpret_cast<' . $info['decl'] . '*>(Z_PTR_P(' . $info['unsafePtr'] . '.ptr()));';
} else {
$code .= '{}';
$code .= $info['decl'] . ' ' . $name;
if ($info['size'] !== null) {
$code .= '(' . $info['size'] . ')';
} else {
$code .= '{}';
}
$code .= ';';
}
$code .= ';';
} elseif ($type === self::TYPE_STD_MAP || $type === self::TYPE_STD_UNORDERED_MAP) {
$info = $this->context->stdContainers[$name];
$code .= $info['decl'] . ' ' . $name . '{};';
if (isset($info['unsafePtr'])) {
$code .= 'auto &' . $name . ' = *reinterpret_cast<' . $info['decl'] . '*>(Z_PTR_P(' . $info['unsafePtr'] . '.ptr()));';
} else {
$code .= $info['decl'] . ' ' . $name . '{};';
}
} else {
$code .= $type . ' ' . $name;
if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) {

@ -24,6 +24,10 @@ class FunctionContext
* @var array<string, array>
*/
public array $stdContainers = [];
/**
* @var array<string, bool>
*/
public array $unsafePtrs = [];
public array $localVars = [];
public array $staticVars = [];
public array $globalVars = [];
@ -58,6 +62,7 @@ class FunctionContext
$this->objects = [];
$this->stdArrays = [];
$this->stdContainers = [];
$this->unsafePtrs = [];
$this->objectProps = [];
$this->ceWrappers = [];
$this->tmpVarIndex = 0;

@ -344,6 +344,72 @@ trait StdContainerParser
return 'php::toObject(' . $valueExpr . ', ' . $this->getClassEntryPtr($class) . ', true)';
}
protected function parseStdUnsafePtr(Expr\StaticCall $expr): string
{
if (count($expr->args) !== 1) {
$this->fatalError($expr, 'std::unsafe_ptr() expects one argument');
}
$arg = $expr->args[0]->value;
if (!$this->isVarExpr($arg)) {
$this->fatalError($expr, 'std::unsafe_ptr() expects a std container variable');
}
$container = $this->parseVariable($arg);
if (!$this->isStdContainer($container)) {
$this->fatalError($expr, 'std::unsafe_ptr() only supports std container variables');
}
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$this->addUnsafePtr($tmpVar);
$expr->setAttribute('unsafePtr', true);
$this->context->beforeStmtLines[] = 'Z_PTR_P(' . $tmpVar . '.ptr()) = &' . $container . ';';
return $tmpVar;
}
protected function parseStdUnsafeCastAssign(string $var, Expr\StaticCall $expr): string
{
if (count($expr->args) !== 2) {
$this->fatalError($expr, 'std::unsafe_cast() expects two arguments');
}
$typeExpr = $expr->args[0]->value;
if (!$this->isStaticCall($typeExpr) || !$this->isNameExpr($typeExpr->class) || !$this->isIdExpr($typeExpr->name) || $typeExpr->class->toString() !== 'std') {
$this->fatalError($expr->args[0]->value, 'std::unsafe_cast() expects first argument to be a std container type expression');
}
$containerType = $typeExpr->name->toString();
if (!in_array($containerType, ['array', 'vector', 'map', 'unordered_map'], true)) {
$this->fatalError($expr->args[0]->value, 'std::unsafe_cast() expects first argument to be a std container type expression');
}
if (!$this->isVarExpr($expr->args[1]->value)) {
$this->fatalError($expr->args[1]->value, 'std::unsafe_cast() expects second argument to be an UnsafePtr variable');
}
$unsafePtr = $this->parseVariable($expr->args[1]->value);
if (!$this->hasVar($unsafePtr)) {
$this->fatalError($expr->args[1]->value, 'Undefined variable `$' . $unsafePtr . '`');
}
if (!$this->isUnsafePtr($unsafePtr)) {
$this->fatalError($expr->args[1]->value, 'std::unsafe_cast() expects second argument to be declared as UnsafePtr');
}
if ($containerType === 'array') {
$this->addLocalVar($var, self::TYPE_STD_ARRAY);
$this->parseStdArray($var, $typeExpr);
$this->context->stdArrays[$var]['unsafePtr'] = $unsafePtr;
return '// reinterpret_cast<' . $this->context->stdArrays[$var]['decl'] . '*>(' . $unsafePtr . ')';
}
if ($containerType === 'vector') {
$this->addLocalVar($var, self::TYPE_STD_VECTOR);
$this->parseStdVector($var, $typeExpr);
} elseif ($containerType === 'map') {
$this->addLocalVar($var, self::TYPE_STD_MAP);
$this->parseStdMap($var, $typeExpr);
} else {
$this->addLocalVar($var, self::TYPE_STD_UNORDERED_MAP);
$this->parseStdUnorderedMap($var, $typeExpr);
}
$this->context->stdContainers[$var]['unsafePtr'] = $unsafePtr;
return '// reinterpret_cast<' . $this->context->stdContainers[$var]['decl'] . '*>(' . $unsafePtr . ')';
}
protected function parseStdMapKeyType(NodeAbstract $expr, string $owner): string
{
if (!$this->isClassConstFetch($expr) || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {

@ -60,6 +60,16 @@ class std
return [];
}
public static function unsafe_ptr(mixed &$value): mixed
{
return null;
}
public static function unsafe_cast(mixed $type, mixed $ptr): mixed
{
return $ptr;
}
public static function fill(array $array, mixed $value): void
{
for ($i = 0; $i < count($array); $i++) {

@ -0,0 +1,25 @@
--TEST--
std array: UnsafePtr unsafe_cast
--FILE--
<?php
function std_array_unsafe_ptr_update(UnsafePtr $unsafePtr): void
{
$array = std::unsafe_cast(std::array(native_types::type_int, 3), $unsafePtr);
var_dump($array[1]);
$array[2] = 9;
}
function main() {
$array = std::array(native_types::type_int, 3);
$array[0] = 1;
$array[1] = 7;
$array[2] = 3;
$ptr = std::unsafe_ptr($array);
std_array_unsafe_ptr_update($ptr);
var_dump($array[2]);
}
?>
--EXPECT--
int(7)
int(9)

@ -0,0 +1,25 @@
--TEST--
std map: UnsafePtr unsafe_cast
--FILE--
<?php
function std_map_unsafe_ptr_update(UnsafePtr $unsafePtr): void
{
$map = std::unsafe_cast(std::map(complex_types::type_str, native_types::type_int), $unsafePtr);
var_dump($map["b"]);
$map["c"] = 9;
}
function main() {
$map = std::map(complex_types::type_str, native_types::type_int);
$map["a"] = 1;
$map["b"] = 7;
$map["c"] = 3;
$ptr = std::unsafe_ptr($map);
std_map_unsafe_ptr_update($ptr);
var_dump($map["c"]);
}
?>
--EXPECT--
int(7)
int(9)

@ -0,0 +1,25 @@
--TEST--
std unordered map: UnsafePtr unsafe_cast
--FILE--
<?php
function std_unordered_map_unsafe_ptr_update(UnsafePtr $unsafePtr): void
{
$map = std::unsafe_cast(std::unordered_map(native_types::type_int, native_types::type_int), $unsafePtr);
var_dump($map[2]);
$map[3] = 9;
}
function main() {
$map = std::unordered_map(native_types::type_int, native_types::type_int);
$map[1] = 1;
$map[2] = 7;
$map[3] = 3;
$ptr = std::unsafe_ptr($map);
std_unordered_map_unsafe_ptr_update($ptr);
var_dump($map[3]);
}
?>
--EXPECT--
int(7)
int(9)

@ -0,0 +1,32 @@
--TEST--
std vector: exact class value type checks typed parameter at runtime
--FILE--
<?php
class StdVectorRuntimeClassValue
{
public function __construct(public int $value)
{
}
}
class StdVectorRuntimeClassValueChild extends StdVectorRuntimeClassValue
{
}
function std_vector_runtime_class_value(StdVectorRuntimeClassValue $value): void
{
$vector = std::vector(StdVectorRuntimeClassValue::class);
try {
$vector[] = $value;
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
}
function main() {
std_vector_runtime_class_value(new StdVectorRuntimeClassValueChild(1));
}
?>
--EXPECT--
The parameter `object` must be instance of class `StdVectorRuntimeClassValue`, object of `StdVectorRuntimeClassValueChild` given

@ -0,0 +1,32 @@
--TEST--
std vector: exact class value type rejects non-object value
--FILE--
<?php
class StdVectorRuntimeScalarValue
{
}
function std_vector_runtime_scalar_mixed(mixed $value): mixed
{
return $value;
}
function main() {
$vector = std::vector(StdVectorRuntimeScalarValue::class);
try {
$vector[] = std_vector_runtime_scalar_mixed(123);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
try {
$vector[] = std_vector_runtime_scalar_mixed(null);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
}
?>
--EXPECT--
The parameter `object` must be `object`, got `int`
The parameter `object` must be `object`, got `null`

@ -0,0 +1,25 @@
--TEST--
std vector: UnsafePtr unsafe_cast
--FILE--
<?php
function std_vector_unsafe_ptr_update(UnsafePtr $unsafePtr): void
{
$vector = std::unsafe_cast(std::vector(native_types::type_int), $unsafePtr);
var_dump($vector[1]);
$vector[2] = 9;
}
function main() {
$vector = std::vector(native_types::type_int, 3);
$vector[0] = 1;
$vector[1] = 7;
$vector[2] = 3;
$ptr = std::unsafe_ptr($vector);
std_vector_unsafe_ptr_update($ptr);
var_dump($vector[2]);
}
?>
--EXPECT--
int(7)
int(9)
Loading…
Cancel
Save