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
1 change: 1 addition & 0 deletions bin/compile.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function run(string $filename, string $code, array $options): void
$debugFile = true === $options['-y'] ? $options['-o'] : $options['-y'];
$runtime->setDebug($debugFile);
}
require_once __DIR__.'/../lib/AOT/LinkerProcessPolyfill.php';
$runtime->standalone($block, $options['-o']);
}
}
Expand Down
46 changes: 46 additions & 0 deletions ext/standard/JitEscapeshellarg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

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

/** LLVM lowering for escapeshellarg() via __compiler_escapeshellarg (#2779). */
final class JitEscapeshellarg
{
/** @return Value */
public static function invoke(Context $context, Value $argStr): Value
{
$quoted = $context->builder->call(
$context->lookupFunction('__compiler_escapeshellarg'),
$argStr
);
$strPtr = $context->getTypeFromString('__string__*');
$failed = $context->builder->icmp(Builder::INT_EQ, $quoted, $strPtr->constNull());

$slot = JitValueBox::alloc($context);
$ptr = JitValueBox::pointer($context, $slot);
$failBlock = BasicBlockHelper::append($context, 'escapeshellarg_fail');
$okBlock = BasicBlockHelper::append($context, 'escapeshellarg_ok');
$doneBlock = BasicBlockHelper::append($context, 'escapeshellarg_done');
$context->builder->branchIf($failed, $failBlock, $okBlock);

$context->builder->positionAtEnd($failBlock);
$context->builder->call($context->lookupFunction('__value__writeNull'), $ptr);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($okBlock);
$owned = $context->builder->call($context->lookupFunction('__string__separate'), $quoted);
$context->builder->call($context->lookupFunction('__value__writeString'), $ptr, $owned);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($doneBlock);

return $ptr;
}
}
72 changes: 72 additions & 0 deletions ext/standard/JitPhpcRunCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\JIT\BasicBlockHelper;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\HashTableHelper;
use PHPCompiler\JIT\JitValueBox;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Builder;
use PHPLLVM\Value;

/** LLVM lowering for phpc_run_command() via __compiler_phpc_run_command (#2779). */
final class JitPhpcRunCommand
{
/** @return Value */
public static function invoke(Context $context, Value $cmdStr, ?JITVariable $envArg): Value
{
$htPtrTy = $context->getTypeFromString('__hashtable__*');
$envHt = $htPtrTy->constNull();
if (null !== $envArg) {
$envHt = self::loadArrayArg($context, $envArg, 2);
}
$resultHt = $context->builder->call(
$context->lookupFunction('__compiler_phpc_run_command'),
$cmdStr,
$envHt
);
$failed = $context->builder->icmp(Builder::INT_EQ, $resultHt, $htPtrTy->constNull());

$slot = JitValueBox::alloc($context);
$ptr = JitValueBox::pointer($context, $slot);
$failBlock = BasicBlockHelper::append($context, 'phpc_run_command_fail');
$okBlock = BasicBlockHelper::append($context, 'phpc_run_command_ok');
$doneBlock = BasicBlockHelper::append($context, 'phpc_run_command_done');
$context->builder->branchIf($failed, $failBlock, $okBlock);

$context->builder->positionAtEnd($failBlock);
$context->builder->call($context->lookupFunction('__value__writeNull'), $ptr);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($okBlock);
$context->builder->call($context->lookupFunction('__value__writeHashtable'), $ptr, $resultHt);
$context->builder->branch($doneBlock);

$context->builder->positionAtEnd($doneBlock);

return $ptr;
}

private static function loadArrayArg(Context $context, JITVariable $arg, int $position): Value
{
if (JITVariable::TYPE_HASHTABLE === $arg->type) {
return $context->helper->loadValue($arg);
}
if (0 !== ($arg->type & JITVariable::IS_NATIVE_ARRAY)) {
return HashTableHelper::materializeNativeArrayForCall($context, $arg);
}
if (JITVariable::TYPE_VALUE === $arg->type) {
return $context->builder->call(
$context->lookupFunction('__value__readHashtable'),
JitValueBox::pointer($context, $arg->value)
);
}

throw new \LogicException(
"phpc_run_command() argument #{$position} must be an array in this compiler build"
);
}
}
2 changes: 2 additions & 0 deletions ext/standard/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ public function getFunctions(): array
new fclose(),
new getenv_(),
new shell_exec(),
new escapeshellarg(),
new phpc_run_command(),
new sys_get_temp_dir(),
new tempnam(),
new getcwd_(),
Expand Down
49 changes: 49 additions & 0 deletions ext/standard/escapeshellarg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

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

/** escapeshellarg() — shell-safe quoting (VM; JIT/AOT via __compiler_escapeshellarg). */
final class escapeshellarg extends Internal
{
public function __construct()
{
parent::__construct('escapeshellarg');
}

public function execute(Frame $frame): void
{
if (1 !== \count($frame->calledArgs)) {
throw new \LogicException('escapeshellarg() requires exactly one argument');
}
$v = $frame->calledArgs[0]->resolveIndirect();
if (null === $frame->returnVar) {
return;
}
if (Variable::TYPE_STRING !== $v->type) {
throw new \LogicException('escapeshellarg() requires a string argument in this compiler build');
}
$frame->returnVar->string(\escapeshellarg($v->toString()));
}

public function call(Context $context, JITVariable ...$args): Value
{
if (1 !== \count($args)) {
throw new \LogicException('escapeshellarg() requires exactly one argument');
}

return JitEscapeshellarg::invoke(
$context,
JitStringArg::lower($context, $args[0], 'escapeshellarg() argument')
);
}
}
96 changes: 96 additions & 0 deletions ext/standard/phpc_run_command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\standard;

use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringArg;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPCompiler\VM\HashTable;
use PHPCompiler\VM\Variable;
use PHPLLVM\Value;

/**
* phpc_run_command() — capture exit code + stdout/stderr for AOT linker (#2779).
*
* Replaces proc_open()/stream_get_contents() in lib/AOT/Linker.php for native self-host.
*/
final class phpc_run_command extends Internal
{
public function __construct()
{
parent::__construct('phpc_run_command');
}

public function execute(Frame $frame): void
{
$argc = \count($frame->calledArgs);
if ($argc < 1 || $argc > 2) {
throw new \LogicException('phpc_run_command() requires one or two arguments');
}
if (null === $frame->returnVar) {
return;
}
$cmdVar = $frame->calledArgs[0]->resolveIndirect();
if (Variable::TYPE_STRING !== $cmdVar->type) {
throw new \LogicException('phpc_run_command() command must be a string');
}
$env = null;
if (2 === $argc) {
$envVar = $frame->calledArgs[1]->resolveIndirect();
if (Variable::TYPE_ARRAY !== $envVar->type) {
throw new \LogicException('phpc_run_command() env must be an array');
}
$env = [];
foreach ($envVar->toArray()->iterateKeyed(true) as $pair) {
[$keyVar, $valVar] = $pair;
if (Variable::TYPE_STRING !== $keyVar->type || Variable::TYPE_STRING !== $valVar->type) {
continue;
}
$env[$keyVar->toString()] = $valVar->toString();
}
}
$descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$proc = \proc_open($cmdVar->toString(), $descriptor, $pipes, null, $env);
if (!\is_resource($proc)) {
$frame->returnVar->null();

return;
}
\fclose($pipes[0]);
$stdout = \stream_get_contents($pipes[1]);
$stderr = \stream_get_contents($pipes[2]);
\fclose($pipes[1]);
\fclose($pipes[2]);
$code = \proc_close($proc);
$ht = new HashTable();
$codeVar = new Variable();
$codeVar->int((int) $code);
$ht->add('code', $codeVar);
$stdoutVar = new Variable();
$stdoutVar->string(false === $stdout ? '' : $stdout);
$ht->add('stdout', $stdoutVar);
$stderrVar = new Variable();
$stderrVar->string(false === $stderr ? '' : $stderr);
$ht->add('stderr', $stderrVar);
$frame->returnVar->array($ht);
}

public function call(Context $context, JITVariable ...$args): Value
{
if (\count($args) < 1 || \count($args) > 2) {
throw new \LogicException('phpc_run_command() requires one or two arguments');
}

$envArg = 2 === \count($args) ? $args[1] : null;

return JitPhpcRunCommand::invoke(
$context,
JitStringArg::lower($context, $args[0], 'phpc_run_command() command'),
$envArg
);
}
}
60 changes: 25 additions & 35 deletions lib/AOT/Linker.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,11 @@ private static function compileRuntimeObjects(string $objectFile): array
$sourceFlags = self::runtimeCIncludeFlagsFor(basename($source));
$cmd = escapeshellarg($compiler).' -c -fPIC -O2'.$sourceFlags.' '
.escapeshellarg($source).' -o '.escapeshellarg($runtimeObject);
$descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$proc = proc_open($cmd, $descriptor, $pipes, null, $env);
if (!is_resource($proc)) {
throw new \LogicException('Failed to start AOT runtime compiler: '.$cmd);
}
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($proc);
if (0 !== $code || !is_file($runtimeObject)) {
$captured = self::runCaptured($cmd, $env);
if (0 !== $captured['code'] || !is_file($runtimeObject)) {
throw new \LogicException(
'Failed to compile AOT runtime: '.trim(
($stderr !== false ? $stderr : '')."\n".($stdout !== false ? $stdout : '')
$captured['stderr']."\n".$captured['stdout']
)
);
}
Expand Down Expand Up @@ -393,16 +383,8 @@ private static function linkWithSystemCompiler(
}
$cmd = escapeshellarg($path) . ' '
. $objects . ' -lm '.self::RUNTIME_LINK_LIBS.' -o ' . escapeshellarg($executable);
$descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$proc = proc_open($cmd, $descriptor, $pipes, null, null);
if (!is_resource($proc)) {
continue;
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($proc);
if (0 === $code) {
$captured = self::runCaptured($cmd, null);
if (0 === $captured['code']) {
self::unlinkIfTemp($runtimeObjects);

return;
Expand All @@ -416,23 +398,31 @@ private static function linkWithSystemCompiler(

private static function run(string $command, array $env): void
{
$descriptor = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$proc = proc_open($command, $descriptor, $pipes, null, $env);
if (!is_resource($proc)) {
throw new \LogicException('Failed to start linker: ' . $command);
}
fclose($pipes[0]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($proc);
if (0 !== $code) {
$captured = self::runCaptured($command, $env);
if (0 !== $captured['code']) {
throw new \LogicException(
'Linking failed (exit ' . $code . '): ' . trim($stderr !== false ? $stderr : '')
'Linking failed (exit '.$captured['code'].'): '.trim($captured['stderr'])
);
}
}

/**
* @return array{code:int,stdout:string,stderr:string}
*/
private static function runCaptured(string $command, ?array $env): array
{
$raw = null === $env ? \phpc_run_command($command) : \phpc_run_command($command, $env);
if (!\is_array($raw)) {
return ['code' => 127, 'stdout' => '', 'stderr' => ''];
}

return [
'code' => (int) ($raw['code'] ?? 127),
'stdout' => \is_string($raw['stdout'] ?? null) ? $raw['stdout'] : '',
'stderr' => \is_string($raw['stderr'] ?? null) ? $raw['stderr'] : '',
];
}

/**
* Repo-relative paths to committed M5 vendor prelink objects (issue #1416).
*
Expand Down
Loading