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
2 changes: 2 additions & 0 deletions ext/standard/readfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Builtin\StringReadfile;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\Variable as JITVariable;
Expand Down Expand Up @@ -44,6 +45,7 @@ public function call(Context $context, JITVariable ...$args): Value
if (1 !== \count($args)) {
throw new \LogicException('readfile() requires exactly one argument in this compiler build');
}
StringReadfile::ensureLinked($context);
$path = JitStreamPath::lowerNonEmptyPath($context, $args[0], 'readfile');

return JitReadfile::invoke($context, $path);
Expand Down
45 changes: 44 additions & 1 deletion lib/JIT/Builtin/StringFileGetContents.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ final class StringFileGetContents
self::READ_HELPER,
];

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

public static function ensureStandaloneBodies(Context $context): void
{
self::implement($context);
}

public static function implement(Context $context): void
{
$probe = $context->module->getNamedFunction(self::ABI);
Expand All @@ -38,6 +48,18 @@ public static function implement(Context $context): void
return;
}

if (self::shouldUseLibcBridge($context)) {
StringFileGetContentsLibc::implement($context);

return;
}

$savedBlock = null;
try {
$savedBlock = $context->builder->getInsertBlock();
} catch (\Throwable) {
}

JitVmHelperLink::ensureCompiled($context, self::HELPER_PATH, self::COMPILED_HELPERS, '#15309');

$strPtr = $context->getTypeFromString('__string__*');
Expand Down Expand Up @@ -73,6 +95,27 @@ public static function implement(Context $context): void
$context->builder->returnValue($strPtr->constNull());

$context->registerFunction(self::ABI, $fn);
$context->builder->clearInsertionPosition();
if (null !== $savedBlock) {
$context->builder->positionAtEnd($savedBlock);
} else {
$context->builder->clearInsertionPosition();
}
}

private static function shouldUseLibcBridge(Context $context): bool
{
if (\PHPCompiler\JIT\Builtin::LOAD_TYPE_STANDALONE !== $context->loadType) {
return false;
}
$userScript = getenv('PHP_COMPILER_AOT_USER_SCRIPT');
if ('1' === $userScript || 'true' === strtolower((string) $userScript)) {
return true;
}
$bootstrapLink = getenv('PHP_COMPILER_BOOTSTRAP_AOT_LINK');
if ('1' === $bootstrapLink || 'true' === strtolower((string) $bootstrapLink)) {
return true;
}

return false;
}
}
211 changes: 211 additions & 0 deletions lib/JIT/Builtin/StringFileGetContentsLibc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT\Builtin;

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

/**
* Thin libc open/read bridge for user-script AOT (#9308, #15309).
*
* Nested-JIT FileGetContentsJitHelper does not run in minimal standalone init;
* restore pre-#15309 LLVM for that gate only. Full JIT/self-host keeps PHP helper path.
* php-src: ext/standard/streamsfuncs.c — php_stream_copy_to_mem
*/
final class StringFileGetContentsLibc
{
private const ABI = '__compiler_file_get_contents';

private const CHUNK = 8192;

private const O_RDONLY = 0;

public static function implement(Context $context): void
{
$probe = $context->module->getNamedFunction(self::ABI);
if (null !== $probe && $probe->countBasicBlocks() > 0) {
$context->registerFunction(self::ABI, $probe);

return;
}

$savedBlock = null;
try {
$savedBlock = $context->builder->getInsertBlock();
} catch (\Throwable) {
}

$fn = null !== $probe
? $probe
: $context->lookupFunction(self::ABI);
$entry = $fn->appendBasicBlock('fgc_libc_entry');
$context->builder->positionAtEnd($entry);

$path = $fn->getParam(0);
$strMap = $context->structFieldMap['__string__'];
$i8 = $context->getTypeFromString('int8');
$i8p = $context->getTypeFromString('int8*');
$i32 = $context->getTypeFromString('int32');
$i64 = $context->getTypeFromString('int64');
$sizeT = $context->getTypeFromString('size_t');
$strPtr = $context->getTypeFromString('__string__*');
$zeroI32 = $i32->constInt(0, false);
$oneI64 = $i64->constInt(1, false);
$chunkSize = $sizeT->constInt(self::CHUNK, false);
$oRdonly = $i32->constInt(self::O_RDONLY, false);
$nullStr = $strPtr->constNull();

$pathLen = $context->builder->load(
$context->builder->structGep($path, $strMap['length'])
);
$pathBytes = $context->builder->structGep($path, $strMap['value']);
$bufLen = $context->builder->add($pathLen, $oneI64);
$pathBuf = $context->builder->call($context->lookupFunction('__mm__malloc'), $bufLen);
$pathCStr = $context->builder->pointerCast($pathBuf, $i8p);
$context->intrinsic->memcpy($pathCStr, $pathBytes, $pathLen, false);
$context->builder->store(
$i8->constInt(0, false),
$context->builder->inBoundsGEP($pathCStr, $pathLen)
);

$fd = $context->builder->call(
$context->lookupFunction('open'),
$pathCStr,
$oRdonly,
$zeroI32
);
$context->builder->call($context->lookupFunction('__mm__free'), $pathBuf);

$openFail = $context->builder->icmp(Builder::INT_SLT, $fd, $zeroI32);
$failBlock = $fn->appendBasicBlock('fgc_libc_open_fail');
$okBlock = $fn->appendBasicBlock('fgc_libc_open_ok');
$context->builder->branchIf($openFail, $failBlock, $okBlock);

$context->builder->positionAtEnd($failBlock);
$context->builder->returnValue($nullStr);

$context->builder->positionAtEnd($okBlock);
$initialBuf = $context->builder->call($context->lookupFunction('__mm__malloc'), $chunkSize);
$dataBufSlot = $context->builder->alloca($i8p, 1, 'fgc_libc_data_buf');
$context->builder->store($initialBuf, $dataBufSlot);

$chunkBuf = $context->builder->call($context->lookupFunction('__mm__malloc'), $chunkSize);
$chunkPtr = $context->builder->pointerCast($chunkBuf, $i8p);

$sizeSlot = $context->builder->alloca($i64, 1, 'fgc_libc_size');
$capSlot = $context->builder->alloca($i64, 1, 'fgc_libc_cap');
$chunkI64 = $context->builder->zExt($chunkSize, $i64);
$context->builder->store($i64->constInt(0, false), $sizeSlot);
$context->builder->store($chunkI64, $capSlot);

$loopHead = BasicBlockHelper::append($context, 'fgc_libc_loop_head');
$loopBody = BasicBlockHelper::append($context, 'fgc_libc_loop_body');
$loopDone = BasicBlockHelper::append($context, 'fgc_libc_loop_done');
$context->builder->branch($loopHead);

$context->builder->positionAtEnd($loopHead);
$nRead = $context->builder->call(
$context->lookupFunction('read'),
$fd,
$chunkPtr,
$chunkSize
);
$noMore = $context->builder->icmp(Builder::INT_SLE, $nRead, $i64->constInt(0, false));
$context->builder->branchIf($noMore, $loopDone, $loopBody);

$context->builder->positionAtEnd($loopBody);
$readErr = $context->builder->icmp(Builder::INT_SLT, $nRead, $i64->constInt(0, false));
$readErrBlock = BasicBlockHelper::append($context, 'fgc_libc_read_err');
$readOkBlock = BasicBlockHelper::append($context, 'fgc_libc_read_ok');
$context->builder->branchIf($readErr, $readErrBlock, $readOkBlock);

$context->builder->positionAtEnd($readErrBlock);
$context->builder->call($context->lookupFunction('close'), $fd);
$context->builder->call(
$context->lookupFunction('__mm__free'),
$context->builder->load($dataBufSlot)
);
$context->builder->call($context->lookupFunction('__mm__free'), $chunkBuf);
$context->builder->returnValue($nullStr);

$context->builder->positionAtEnd($readOkBlock);
$size = $context->builder->load($sizeSlot);
$cap = $context->builder->load($capSlot);
$needed = $context->builder->add($size, $nRead);
$needGrow = $context->builder->icmp(Builder::INT_SGT, $needed, $cap);
$growBlock = BasicBlockHelper::append($context, 'fgc_libc_grow');
$appendBlock = BasicBlockHelper::append($context, 'fgc_libc_append');
$context->builder->branchIf($needGrow, $growBlock, $appendBlock);

$context->builder->positionAtEnd($growBlock);
$doubled = $context->builder->mul($cap, $i64->constInt(2, false));
$newCap = $context->builder->select(
$context->builder->icmp(Builder::INT_SGT, $doubled, $needed),
$doubled,
$needed
);
$newCapSizeT = $context->builder->truncOrBitCast($newCap, $sizeT);
$grown = $context->builder->call(
$context->lookupFunction('__mm__realloc'),
$context->builder->load($dataBufSlot),
$newCapSizeT
);
$grownNull = $context->builder->icmp(Builder::INT_EQ, $grown, $i8p->constNull());
$reallocFailBlock = BasicBlockHelper::append($context, 'fgc_libc_realloc_fail');
$reallocOkBlock = BasicBlockHelper::append($context, 'fgc_libc_realloc_ok');
$context->builder->branchIf($grownNull, $reallocFailBlock, $reallocOkBlock);

$context->builder->positionAtEnd($reallocFailBlock);
$context->builder->call($context->lookupFunction('close'), $fd);
$context->builder->call(
$context->lookupFunction('__mm__free'),
$context->builder->load($dataBufSlot)
);
$context->builder->call($context->lookupFunction('__mm__free'), $chunkBuf);
$context->builder->returnValue($nullStr);

$context->builder->positionAtEnd($reallocOkBlock);
$context->builder->store($grown, $dataBufSlot);
$context->builder->store($newCap, $capSlot);
$context->builder->branch($appendBlock);

$context->builder->positionAtEnd($appendBlock);
$size = $context->builder->load($sizeSlot);
$dataPtr = $context->builder->pointerCast($context->builder->load($dataBufSlot), $i8p);
$destAt = $context->builder->gep($dataPtr, $size);
$nSizeT = $context->builder->truncOrBitCast($nRead, $sizeT);
$context->intrinsic->memcpy($destAt, $chunkPtr, $nSizeT, false);
$context->builder->store(
$context->builder->add($size, $nRead),
$sizeSlot
);
$context->builder->branch($loopHead);

$context->builder->positionAtEnd($loopDone);
$context->builder->call($context->lookupFunction('close'), $fd);
$context->builder->call($context->lookupFunction('__mm__free'), $chunkBuf);

$finalSize = $context->builder->load($sizeSlot);
$dataPtr = $context->builder->pointerCast($context->builder->load($dataBufSlot), $i8p);
$result = $context->builder->call(
$context->lookupFunction('__string__init'),
$finalSize,
$dataPtr
);
$context->builder->call(
$context->lookupFunction('__mm__free'),
$context->builder->load($dataBufSlot)
);
$context->builder->returnValue($result);

$context->registerFunction(self::ABI, $fn);
if (null !== $savedBlock) {
$context->builder->positionAtEnd($savedBlock);
} else {
$context->builder->clearInsertionPosition();
}
}
}
12 changes: 11 additions & 1 deletion lib/JIT/Builtin/StringReadfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public static function implement(Context $context): void
return;
}

$savedBlock = null;
try {
$savedBlock = $context->builder->getInsertBlock();
} catch (\Throwable) {
}

$fn = null !== $probe
? $probe
: $context->lookupFunction('__compiler_readfile');
Expand All @@ -58,7 +64,11 @@ public static function implement(Context $context): void
);
$context->builder->returnValue($result);
$context->registerFunction('__compiler_readfile', $fn);
$context->builder->clearInsertionPosition();
if (null !== $savedBlock) {
$context->builder->positionAtEnd($savedBlock);
} else {
$context->builder->clearInsertionPosition();
}
}

private static function helperFunction(Context $context): LlvmFunction
Expand Down
2 changes: 2 additions & 0 deletions lib/JIT/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,8 @@ private function ensureMinimalUserStandaloneBodies(): void
Builtin\RewriteVarsRuntime::ensureStandaloneBodies($this);
Builtin\DefineRuntime::ensureStandaloneBodies($this);
Builtin\StringStrContains::ensureStandaloneBodies($this);
Builtin\StringFileGetContents::ensureStandaloneBodies($this);
Builtin\StringReadfile::ensureStandaloneBodies($this);
Builtin\SuperglobalNameRuntime::ensureLinked($this);
}

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/aot/cases/file_get_contents.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
--TEST--
AOT: file_get_contents() reads a file into a string
--ENV--
REQUEST_METHOD=
--FILE--
<?php
$path = 'test/compliance/cases/stdlib/readfile_fixture/data.txt';
Expand Down