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
779 changes: 349 additions & 430 deletions docs/bootstrap-inventory.md

Large diffs are not rendered by default.

24 changes: 1 addition & 23 deletions ext/standard/JitRename.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,20 @@
declare(strict_types=1);

/**
* JIT/AOT helper for rename() — libc rename(2) on user-script AOT, else RenameJitHelper (#16734).
* JIT/AOT helper for rename() via RenameJitHelper PHP (#15533, #19215).
*/

namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\Builtin\StringRename;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\UserScriptAotDeferNestedJit;
use PHPLLVM\Builder;
use PHPLLVM\Value;

final class JitRename
{
/** @return Value */
public static function invoke(Context $context, Value $fromStr, Value $toStr): Value
{
if (UserScriptAotDeferNestedJit::shouldDefer($context)) {
return self::invokeLibc($context, $fromStr, $toStr);
}

return StringRename::invoke($context, $fromStr, $toStr);
}

private static function invokeLibc(Context $context, Value $fromStr, Value $toStr): Value
{
$map = $context->structFieldMap['__string__'];
$fromPtr = $context->builder->structGep($fromStr, $map['value']);
$toPtr = $context->builder->structGep($toStr, $map['value']);
$i32 = $context->getTypeFromString('int32');
$ret = $context->builder->call(
$context->lookupFunction('rename'),
$fromPtr,
$toPtr
);
$zero = $i32->constInt(0, false);

return $context->builder->icmp(Builder::INT_EQ, $ret, $zero);
}
}
35 changes: 35 additions & 0 deletions ext/standard/JitRenameKernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\Context;
use PHPLLVM\Builder;
use PHPLLVM\Value;

/**
* LLVM lowering for phpc_rename_kernel() — thin libc rename(2) (#19215).
*
* Used inside RenameJitHelper / VmFsPathPure so nested helper units do not
* recurse through the rename() builtin bridge.
*/
final class JitRenameKernel
{
/** @return Value i1 — true when rename(2) returns 0 */
public static function invoke(Context $context, Value $fromStr, Value $toStr): Value
{
$map = $context->structFieldMap['__string__'];
$fromPtr = $context->builder->structGep($fromStr, $map['value']);
$toPtr = $context->builder->structGep($toStr, $map['value']);
$i32 = $context->getTypeFromString('int32');
$ret = $context->builder->call(
$context->lookupFunction('rename'),
$fromPtr,
$toPtr
);
$zero = $i32->constInt(0, false);

return $context->builder->icmp(Builder::INT_EQ, $ret, $zero);
}
}
98 changes: 98 additions & 0 deletions ext/standard/JitStatKernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\Context;
use PHPLLVM\Builder;
use PHPLLVM\Value;

/**
* Thin libc stat(2)/lstat(2)/access(2) for path predicates (#19215).
*
* Standalone mode helpers avoid LLVM miscompile when nested into helper TUs (#8555).
* Keep glibc layout here — not in {@see JitStat} (#9112 shrink).
* php-src: ext/standard/filestat.c
*/
final class JitStatKernel
{
/** sizeof(struct stat) on Linux x86_64 glibc */
private const STAT_BUF_SIZE = 144;

/** offsetof(struct stat, st_mode) on Linux x86_64 glibc */
private const STAT_MODE_OFFSET = 24;

/** @return Value i32 — st_mode, or -1 on failure */
public static function mode(Context $context, Value $pathStr, bool $useLstat): Value
{
$statFn = $useLstat ? 'lstat' : 'stat';
$fn = self::ensureModeStandalone($context, $statFn);

return $context->builder->call($fn, $pathStr);
}

/** @return Value i1 — access(2) succeeds */
public static function accessOk(Context $context, Value $pathStr, int $mode): Value
{
$map = $context->structFieldMap['__string__'];
$pathPtr = $context->builder->structGep($pathStr, $map['value']);
$i32 = $context->getTypeFromString('int32');
$ret = $context->builder->call(
$context->lookupFunction('access'),
$pathPtr,
$i32->constInt($mode, false)
);

return $context->builder->icmp(Builder::INT_EQ, $ret, $i32->constInt(0, false));
}

private static function ensureModeStandalone(Context $context, string $statFn): Value
{
$name = '__phpc_jit_stat_mode_kernel_'.$statFn;
$existing = $context->module->getNamedFunction($name);
if (null !== $existing && $existing->countBasicBlocks() > 0) {
$context->registerFunction($name, $existing);

return $existing;
}

$strPtr = $context->getTypeFromString('__string__*');
$i32 = $context->getTypeFromString('int32');
$fn = $context->module->addFunction(
$name,
$context->context->functionType($i32, false, $strPtr)
);
$entry = $fn->appendBasicBlock('entry');
$saved = $context->builder;
$context->builder = $context->context->builderCreate();
$context->builder->positionAtEnd($entry);

$str = $fn->getParam(0);
$map = $context->structFieldMap['__string__'];
$pathPtr = $context->builder->structGep($str, $map['value']);
$i8 = $context->getTypeFromString('int8');
$i8p = $context->getTypeFromString('int8*');
$i64 = $context->getTypeFromString('int64');
$bufType = $i8->arrayType(self::STAT_BUF_SIZE);
$buf = $context->builder->alloca($bufType, 1, 'phpc_stat_mode_buf');
$bufPtr = $context->builder->pointerCast($buf, $i8p);
$ret = $context->builder->call(
$context->lookupFunction($statFn),
$pathPtr,
$bufPtr
);
$zero = $i32->constInt(0, false);
$failed = $context->builder->icmp(Builder::INT_NE, $ret, $zero);
$bytePtr = $context->builder->gep($bufPtr, $i64->constInt(self::STAT_MODE_OFFSET, false));
$modePtr = $context->builder->pointerCast($bytePtr, $i32->pointerType(0));
$mode = $context->builder->load($modePtr);
$minusOne = $i32->constInt(-1, true);
$context->builder->returnValue($context->builder->select($failed, $minusOne, $mode));
$context->builder->clearInsertionPosition();
$context->builder = $saved;
$context->registerFunction($name, $fn);

return $fn;
}
}
3 changes: 3 additions & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,9 @@ public function getFunctions(): array
new phpc_native_ht_set_hashtable_at(),
new phpc_native_ht_set_string_key_long(),
new phpc_native_environ_mirror_into_ht(),
new phpc_rename_kernel(),
new phpc_stat_mode_kernel(),
new phpc_access_kernel(),
new sys_get_temp_dir(),
new sys_getloadavg(),
new openlog(),
Expand Down
14 changes: 12 additions & 2 deletions ext/standard/RenameJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@
/**
* rename() for compiled JIT/AOT modules (#15533, php-in-PHP).
*
* SSOT: {@see VmFs::rename()}
* Kernel path: {@see phpc_rename_kernel}; VM SSOT remains VmFs rename.
* php-src: ext/standard/filestat.c — php_rename
*/
final class RenameJitHelper
{
public static function invokeArgv(string $from, string $to): bool
{
$ok = VmFs::rename($from, $to);
if (str_contains($from, "\0") || str_contains($to, "\0")) {
$ok = false;
} elseif (null !== VmFsPhpWrapper::renameWarningMessage($from, $to)) {
$ok = false;
} else {
$ok = \phpc_rename_kernel($from, $to);
}
if ($ok) {
VmStatCache::invalidatePath($from);
VmStatCache::invalidatePath($to);
}
if (!$ok) {
$wrapperMessage = VmFsPhpWrapper::renameWarningMessage($from, $to);
TriggerErrorJitHelper::warning(
Expand Down
27 changes: 18 additions & 9 deletions ext/standard/StatPathJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,54 @@
namespace PHPCompiler\ext\standard;

/**
* File path predicates for compiled JIT/AOT modules (#9112, php-in-PHP).
* File path predicates for compiled JIT/AOT modules (#9112, #19215, php-in-PHP).
*
* VM SSOT: {@see VmStatPath}
* User-script AOT: thin {@see phpc_stat_mode_kernel} / {@see phpc_access_kernel}
* (libc) — VmStatPath is an external stub in nested helper TUs so `@\\stat` is unused.
* VM SSOT for non-compiled paths remains {@see VmStatPath}.
* php-src: ext/standard/filestat.c
*/
final class StatPathJitHelper
{
public static function exists(string $path): bool
{
return VmStatPath::exists($path);
return \phpc_stat_mode_kernel($path, 0) >= 0;
}

public static function isFile(string $path): bool
{
return VmStatPath::isFile($path);
$mode = \phpc_stat_mode_kernel($path, 0);

// S_IFMT=0xF000, S_IFREG=0x8000 — literals so nested JIT folds cleanly (#19215).
return $mode >= 0 && ($mode & 0xF000) === 0x8000;
}

public static function isDir(string $path): bool
{
return VmStatPath::isDir($path);
$mode = \phpc_stat_mode_kernel($path, 0);

return $mode >= 0 && ($mode & 0xF000) === 0x4000;
}

public static function isLink(string $path): bool
{
return VmStatPath::isLink($path);
$mode = \phpc_stat_mode_kernel($path, 1);

return $mode >= 0 && ($mode & 0xF000) === 0xA000;
}

public static function isReadable(string $path): bool
{
return VmStatPath::isReadable($path);
return \phpc_access_kernel($path, 4);
}

public static function isWritable(string $path): bool
{
return VmStatPath::isWritable($path);
return \phpc_access_kernel($path, 2);
}

public static function isExecutable(string $path): bool
{
return VmStatPath::isExecutable($path);
return \phpc_access_kernel($path, 1);
}
}
70 changes: 70 additions & 0 deletions ext/standard/phpc_access_kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* @internal libc access(2) kernel for StatPathJitHelper (#19215).
*
* mode is POSIX R_OK/W_OK/X_OK (or F_OK).
* php-src: ext/standard/filestat.c — php_access
*/
final class phpc_access_kernel extends Internal
{
public function __construct()
{
parent::__construct('phpc_access_kernel');
}

public function execute(Frame $frame): void
{
$argc = \count($frame->calledArgs);
if (2 !== $argc) {
throw new \LogicException('phpc_access_kernel() expects exactly 2 arguments, '.$argc.' given');
}
$path = VmFilestatArg::coerceFilenameArg($frame->calledArgs[0], 'phpc_access_kernel', 0, 'filename', $frame);
$mode = $frame->calledArgs[1]->toInt();
$ok = false;
if (!str_contains($path, "\0") && \function_exists('file_exists')) {
// Mirror access(2): F_OK via file_exists; R/W/X via is_* when hosted under Zend.
if (0 === $mode) {
$ok = @\file_exists($path);
} elseif (4 === $mode) {
$ok = @\is_readable($path);
} elseif (2 === $mode) {
$ok = @\is_writable($path);
} elseif (1 === $mode) {
$ok = @\is_executable($path);
}
}
if (null !== $frame->returnVar) {
$frame->returnVar->bool($ok);
}
}

public function call(Context $context, JITVariable ...$args): Value
{
if (2 !== \count($args)) {
throw new \LogicException('phpc_access_kernel() expects exactly 2 arguments');
}
$path = JitStringBuiltinArg::lowerPath($context, $args[0], 'phpc_access_kernel', 0, 'filename');
$mode = 0;
if (JITVariable::TYPE_NATIVE_LONG === $args[1]->type && JITVariable::KIND_VALUE === $args[1]->kind) {
try {
$mode = (int) $args[1]->value->getConstantValue();
} catch (\Throwable) {
$mode = 0;
}
}

return JitStatKernel::accessOk($context, $path, $mode);
}
}
48 changes: 48 additions & 0 deletions ext/standard/phpc_rename_kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* @internal libc rename(2) kernel for VmFsPathPure / RenameJitHelper (#19215).
*/
final class phpc_rename_kernel extends Internal
{
public function __construct()
{
parent::__construct('phpc_rename_kernel');
}

public function execute(Frame $frame): void
{
$argc = \count($frame->calledArgs);
if (2 !== $argc) {
throw new \LogicException('phpc_rename_kernel() expects exactly 2 arguments, '.$argc.' given');
}
$from = VmFilestatArg::coerceFilenameArg($frame->calledArgs[0], 'phpc_rename_kernel', 0, 'from', $frame);
$to = VmFilestatArg::coerceFilenameArg($frame->calledArgs[1], 'phpc_rename_kernel', 1, 'to', $frame);
$ok = @\rename($from, $to);
if (null !== $frame->returnVar) {
$frame->returnVar->bool($ok);
}
}

public function call(Context $context, JITVariable ...$args): Value
{
if (2 !== \count($args)) {
throw new \LogicException('phpc_rename_kernel() expects exactly 2 arguments');
}
$from = JitStringBuiltinArg::lowerPath($context, $args[0], 'phpc_rename_kernel', 0, 'from');
$to = JitStringBuiltinArg::lowerPath($context, $args[1], 'phpc_rename_kernel', 1, 'to');

return JitRenameKernel::invoke($context, $from, $to);
}
}
Loading
Loading