Skip to content
Closed
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
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);
}
}
1 change: 1 addition & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ 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 sys_get_temp_dir(),
new sys_getloadavg(),
new openlog(),
Expand Down
12 changes: 11 additions & 1 deletion ext/standard/RenameJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ 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
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);
}
}
64 changes: 50 additions & 14 deletions lib/JIT/Builtin/StringRename.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\UserScriptAotDeferNestedJit;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPCompiler\JIT\NestedJitCompileScope;
use PHPCompiler\ext\standard\JitRenameKernel;
use PHPLLVM\Value;

/**
* JIT/AOT link for rename() via RenameJitHelper PHP or libc for user-script AOT (#16734).
* JIT/AOT link for rename() via phpc_rename_kernel + stat cache (#15533, #19215).
*
* SSOT: {@see \PHPCompiler\ext\standard\VmFs::rename()}.
* User-script AOT uses {@see JitRenameKernel} libc rename(2) with stat invalidation;
* {@see \PHPCompiler\ext\standard\RenameJitHelper} remains the php-in-PHP SSOT for
* warnings/guards when nested-compiled (helper-runtime unit).
* php-src: ext/standard/filestat.c — php_rename
*/
final class StringRename
Expand All @@ -31,9 +35,6 @@ final class StringRename

public static function ensureLinked(Context $context): void
{
if (UserScriptAotDeferNestedJit::shouldDefer($context)) {
return;
}
self::implement($context);
}

Expand All @@ -51,14 +52,25 @@ public static function invoke(Context $context, Value $from, Value $to): Value

private static function implement(Context $context): void
{
if (NestedJitCompileScope::isActive()) {
return;
}

$probe = $context->module->getNamedFunction(self::ABI);
if (JitVmHelperLink::hasNamedBridgeEntry($probe, self::BRIDGE_ENTRY)) {
$context->registerFunction(self::ABI, $probe);

return;
}

JitVmHelperLink::ensureCompiled($context, self::HELPER_PATH, self::COMPILED_HELPERS, '#15533');
$savedBlock = null;
try {
$savedBlock = $context->builder->getInsertBlock();
} catch (\Throwable) {
}

JitVmHelperLink::ensureCompiled($context, self::HELPER_PATH, self::COMPILED_HELPERS, '#19215');
StatCacheRuntime::ensureLinked($context);

$strPtr = $context->getTypeFromString('__string__*');
$i1 = $context->getTypeFromString('int1');
Expand All @@ -69,15 +81,39 @@ private static function implement(Context $context): void
$context->context->functionType($i1, false, $strPtr, $strPtr)
);

$entry = $fn->appendBasicBlock(self::BRIDGE_ENTRY);
$entry = JitVmHelperLink::bridgeEntryForEmit($fn, self::BRIDGE_ENTRY);
$okBlock = $fn->appendBasicBlock('rename_bridge_ok');
$done = $fn->appendBasicBlock('rename_bridge_done');
$context->builder->positionAtEnd($entry);

$helperFn = JitVmHelperLink::lookupCompiled($context, self::INVOKE_HELPER, '#15533');
$raw = JitNestedHelperCoerce::callHelper($context, $helperFn, [$fn->getParam(0), $fn->getParam(1)]);
$context->builder->returnValue(
JitNestedHelperCoerce::coerceHelperScalarResult($context, $raw, $i1)
);
$from = $fn->getParam(0);
$to = $fn->getParam(1);
$ok = JitRenameKernel::invoke($context, $from, $to);
$context->builder->branchIf($ok, $okBlock, $done);

$context->builder->positionAtEnd($okBlock);
$i64 = $context->getTypeFromString('int64');
$clearRealpath = $i64->constInt(1, false);
$clearPathHelper = $context->functions['phpcompiler\\ext\\standard\\statcachejithelper::clearpath'] ?? null;
if (null === $clearPathHelper) {
throw new \LogicException('StatCacheJitHelper::clearPath missing for rename bridge (#19215)');
}
$context->builder->call($clearPathHelper, $clearRealpath, $from);
$context->builder->call($clearPathHelper, $clearRealpath, $to);
$okEnd = $context->builder->getInsertBlock();
$context->builder->branch($done);

$context->builder->positionAtEnd($done);
$ret = $context->builder->phi($i1, 'rename_bridge_result');
$ret->addIncoming($ok, $entry);
$ret->addIncoming($ok, $okEnd);
$context->builder->returnValue($ret);
$context->registerFunction(self::ABI, $fn);
$context->builder->clearInsertionPosition();

if (null !== $savedBlock) {
$context->builder->positionAtEnd($savedBlock);
} else {
$context->builder->clearInsertionPosition();
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"fingerprint":"b2e3d0dc5c2fd2128500","unit":"/ext/standard/RenameJitHelper.php","helpers":{"phpcompiler\\ext\\standard\\renamejithelper::invokeargv":"phpcompiler_ext_standard_renamejithelper__invokeargv"},"init_symbol":"__init__unit_ext_standard_RenameJitHelper_php","shutdown_symbol":"__shutdown__unit_ext_standard_RenameJitHelper_php","init_via_global_ctor":true,"runtime_safe":true}
{"fingerprint":"c2e3d15b9c70bbb71e64","unit":"/ext/standard/RenameJitHelper.php","helpers":{"phpcompiler\\ext\\standard\\renamejithelper::invokeargv":"phpcompiler_ext_standard_renamejithelper__invokeargv"},"init_symbol":"__init__unit_ext_standard_RenameJitHelper_php","shutdown_symbol":"__shutdown__unit_ext_standard_RenameJitHelper_php","init_via_global_ctor":true,"runtime_safe":true}
Binary file not shown.
13 changes: 13 additions & 0 deletions test/unit/RenameRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ public function testJitRenameUsesPhpBridgeNotLibc(): void
{
$source = (string) file_get_contents(__DIR__.'/../../ext/standard/JitRename.php');
$this->assertStringContainsString('StringRename::invoke', $source);
$this->assertStringNotContainsString('invokeLibc', $source);
$this->assertStringNotContainsString("lookupFunction('rename')", $source);
$this->assertStringNotContainsString('UserScriptAotDeferNestedJit', $source);
}

public function testStringRenameNoLibcDefer(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/StringRename.php');
$this->assertStringContainsString('JitRenameKernel', $source);
$this->assertStringContainsString('StatCacheRuntime', $source);
$this->assertStringNotContainsString('UserScriptAotDeferNestedJit', $source);
}

public function testStringRenameBridgeUsesRenameJitHelper(): void
Expand All @@ -26,6 +36,9 @@ public function testStringRenameBridgeUsesRenameJitHelper(): void

public function testRenameJitHelperDelegatesToVmFs(): void
{
if (!\function_exists('phpc_rename_kernel')) {
$this->markTestSkipped('phpc_rename_kernel requires compiler runtime');
}
$dir = sys_get_temp_dir().'/phpc-rename-'.bin2hex(random_bytes(4));
$this->assertTrue(mkdir($dir, 0700));
$from = $dir.'/from.txt';
Expand Down
8 changes: 8 additions & 0 deletions test/unit/VmFsPathPureRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public function testVmFsPathPureDoesNotUseLibcFfi(): void
$this->assertStringNotContainsString('int rename(const char', $source);
}

public function testRenameJitHelperUsesRenameKernel(): void
{
$source = (string) file_get_contents(__DIR__.'/../../ext/standard/RenameJitHelper.php');
$this->assertStringContainsString('phpc_rename_kernel', $source);
$this->assertStringContainsString('VmStatCache::invalidatePath', $source);
$this->assertStringNotContainsString('VmFs::rename', $source);
}

public function testRenameCopyLinkRoundTripViaNativeDelegate(): void
{
if (!VmFsPathPure::available()) {
Expand Down