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
139 changes: 139 additions & 0 deletions ext/mbstring/JitMbOutputHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\ext\mbstring;

use PHPCompiler\JIT\BasicBlockHelper;
use PHPCompiler\JIT\Builtin\MbConvertEncodingRuntime;
use PHPCompiler\JIT\Builtin\MbHttpOutputRuntime;
use PHPCompiler\JIT\Builtin\MbInternalEncodingRuntime;
use PHPCompiler\JIT\Builtin\MbOutputHandlerRuntime;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitNestedHelperCoerce;
use PHPCompiler\JIT\JitStrictIntArg;
use PHPCompiler\JIT\JitStringArg;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\JitValueBox;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* LLVM JIT/AOT for mb_output_handler() (php-src ext/mbstring/mbstring.c; #20014).
*
* Compile-time fold for literal args; runtime via encoding globals + NestedJIT
* {@see MbOutputHandlerJitHelper} (peer {@see JitMbGetInfo}).
*/
final class JitMbOutputHandler
{
/**
* @param list<JITVariable> $args
*/
public static function invoke(Context $context, array $args): Value
{
if (2 !== \count($args)) {
throw new \ArgumentCountError(sprintf(
'mb_output_handler() expects exactly 2 arguments, %d given',
\count($args)
));
}

$stringLit = JitStringArg::compileTimeLiteral($args[0]);
$statusLit = self::compileTimeStatus($context, $args[1]);
if (null !== $stringLit && null !== $statusLit) {
return self::materializeString(
$context,
self::foldOutputHandler($context, $stringLit, $statusLit)
);
}

return self::lowerRuntime($context, $args[0]);
}

private static function foldOutputHandler(Context $context, string $string, int $status): string
{
$httpOutput = MbstringAotFoldState::httpOutput($context) ?? (string) MbstringState::httpOutput();
if (0 === strcasecmp($httpOutput, 'pass')) {
return $string;
}
$from = MbstringAotFoldState::internalEncoding($context) ?? MbstringState::internalEncoding();
if (0 === strcasecmp($from, $httpOutput)) {
return $string;
}
$converted = VmMbstring::convertEncoding($string, $httpOutput, $from);
if (false === $converted) {
return $string;
}

return $converted;
}

private static function lowerRuntime(Context $context, JITVariable $stringArg): Value
{
$savedInsert = BasicBlockHelper::tryGetInsertBlock($context);
MbHttpOutputRuntime::ensureLinked($context);
MbInternalEncodingRuntime::ensureLinked($context);
MbOutputHandlerRuntime::ensureLinked($context);
MbConvertEncodingRuntime::ensureLinked($context);
if (null !== $savedInsert) {
BasicBlockHelper::restoreInsertBlock($context, $savedInsert);
}
BasicBlockHelper::ensureOpenInsertBlock($context, 'mb_output_handler_runtime');

$string = JitStringBuiltinArg::lower(
$context,
$stringArg,
'mb_output_handler',
0,
'string'
);

$httpCode = $context->builder->load(MbHttpOutputRuntime::encodingCodeGlobal($context));
$internalCode = $context->builder->load(MbInternalEncodingRuntime::encodingCodeGlobal($context));
$raw = JitNestedHelperCoerce::callHelper(
$context,
MbOutputHandlerRuntime::convertHelper($context),
[$string, $httpCode, $internalCode]
);
$resultStr = JitNestedHelperCoerce::extractStringPtrFromHelperResult($context, $raw);

return self::materializeOwnedString($context, $resultStr);
}

private static function compileTimeStatus(Context $context, JITVariable $arg): ?int
{
if (JITVariable::TYPE_NATIVE_LONG === $arg->type && JITVariable::KIND_VALUE === $arg->kind) {
$lib = $context->llvm->lib;
if (null !== $lib->LLVMIsAConstantInt($arg->value->value)) {
return (int) $lib->LLVMConstIntGetSExtValue($arg->value->value);
}
}
$constName = $arg->compileTimeConstantName ?? null;
if (null !== $constName && null !== $context->runtime->vmContext) {
$phpVar = $context->runtime->vmContext->constantFetch($constName);
if (null !== $phpVar && \PHPCompiler\VM\Variable::TYPE_INTEGER === $phpVar->type) {
return $phpVar->toInt();
}
}

return null;
}

private static function materializeString(Context $context, string $str): Value
{
return self::materializeOwnedString(
$context,
$context->builder->load($context->constantStringFromString($str))
);
}

private static function materializeOwnedString(Context $context, Value $resultStr): Value
{
$owned = $context->builder->call($context->lookupFunction('__string__separate'), $resultStr);
$slot = JitValueBox::alloc($context);
$ptr = JitValueBox::pointer($context, $slot);
$context->builder->call($context->lookupFunction('__value__writeString'), $ptr, $owned);

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

declare(strict_types=1);

namespace PHPCompiler\ext\mbstring;

/**
* mb_output_handler() NestedJIT runtime (#20014 leftover — compile-time-only args blocked AOT).
*
* Encoding is passed as int codes from module globals (peer {@see MbHttpOutputJitHelper}).
* php-src: ext/mbstring/mbstring.c — PHP_FUNCTION(mb_output_handler)
*/
final class MbOutputHandlerJitHelper
{
public static function nameFromCodeArgv(int $code): string
{
return match ($code) {
MbHttpOutputJitHelper::CODE_UTF8 => 'UTF-8',
MbHttpOutputJitHelper::CODE_ASCII => 'ASCII',
MbHttpOutputJitHelper::CODE_ISO88591 => 'ISO-8859-1',
MbHttpOutputJitHelper::CODE_SJIS => 'SJIS',
MbHttpOutputJitHelper::CODE_EUCJP => 'EUC-JP',
MbHttpOutputJitHelper::CODE_8BIT => '8BIT',
MbHttpOutputJitHelper::CODE_PASS => 'pass',
default => 'UTF-8',
};
}

public static function convertArgv(string $string, int $httpCode, int $internalCode): string
{
if (MbHttpOutputJitHelper::CODE_PASS === $httpCode) {
return $string;
}
$http = self::nameFromCodeArgv($httpCode);
$from = self::nameFromCodeArgv($internalCode);
if (0 === strcasecmp($from, $http)) {
return $string;
}

return MbConvertEncodingJitHelper::convertArgv($string, $http, $from);
}
}
58 changes: 3 additions & 55 deletions ext/mbstring/mb_output_handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringArg;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

/**
* mb_output_handler() — OB callback converting to mb_http_output encoding
* (php-src ext/mbstring/mbstring.c; #20014).
*
* JIT/AOT: compile-time fold + NestedJIT via {@see JitMbOutputHandler}.
*/
final class mb_output_handler extends Internal
{
Expand Down Expand Up @@ -49,59 +50,6 @@ public function execute(Frame $frame): void

public function call(Context $context, JITVariable ...$args): Value
{
if (2 !== \count($args)) {
throw new \ArgumentCountError(sprintf(
'mb_output_handler() expects exactly 2 arguments, %d given',
\count($args)
));
}

$stringLit = JitStringArg::compileTimeLiteral($args[0]);
$statusLit = self::compileTimeStatus($context, $args[1]);
if (null === $stringLit || null === $statusLit) {
throw new \LogicException(
'mb_output_handler() requires compile-time string and int arguments in this compiler build'
);
}
$out = self::foldOutputHandler($context, $stringLit, $statusLit);

return $context->builder->load($context->constantStringFromString($out));
}

private static function foldOutputHandler(Context $context, string $string, int $status): string
{
$httpOutput = MbstringAotFoldState::httpOutput($context) ?? (string) MbstringState::httpOutput();
if (0 === strcasecmp($httpOutput, 'pass')) {
return $string;
}
$from = MbstringAotFoldState::internalEncoding($context) ?? MbstringState::internalEncoding();
if (0 === strcasecmp($from, $httpOutput)) {
return $string;
}
$converted = VmMbstring::convertEncoding($string, $httpOutput, $from);
if (false === $converted) {
return $string;
}

return $converted;
}

private static function compileTimeStatus(Context $context, JITVariable $arg): ?int
{
if (JITVariable::TYPE_NATIVE_LONG === $arg->type && JITVariable::KIND_VALUE === $arg->kind) {
$lib = $context->llvm->lib;
if (null !== $lib->LLVMIsAConstantInt($arg->value->value)) {
return (int) $lib->LLVMConstIntGetSExtValue($arg->value->value);
}
}
$constName = $arg->compileTimeConstantName ?? null;
if (null !== $constName && null !== $context->runtime->vmContext) {
$phpVar = $context->runtime->vmContext->constantFetch($constName);
if (null !== $phpVar && \PHPCompiler\VM\Variable::TYPE_INTEGER === $phpVar->type) {
return $phpVar->toInt();
}
}

return null;
return JitMbOutputHandler::invoke($context, $args);
}
}
74 changes: 74 additions & 0 deletions lib/JIT/Builtin/MbOutputHandlerRuntime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\JIT\Builtin;

use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitVmHelperLink;
use PHPLLVM\Value;
use PHPLLVM\Value\Function_ as LlvmFunction;

/**
* JIT/AOT link hook for mb_output_handler() — MbOutputHandlerJitHelper (#20014 runtime args).
*
* php-src: ext/mbstring/mbstring.c — PHP_FUNCTION(mb_output_handler)
*/
final class MbOutputHandlerRuntime
{
private const HELPER_PATH = '/ext/mbstring/MbOutputHandlerJitHelper.php';

private const CONVERT_LOGICAL = 'PHPCompiler\\ext\\mbstring\\MbOutputHandlerJitHelper::convertArgv';

public const G_OUTCONV_ENABLED = '__phpc_mb_output_handler_outconv_enabled';

/** @var list<string> */
private const COMPILED_HELPERS = [
self::CONVERT_LOGICAL,
];

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

public static function convertHelper(Context $context): LlvmFunction
{
self::ensureJitHelperCompiled($context);

return JitVmHelperLink::lookupCompiled($context, self::CONVERT_LOGICAL, '#20014');
}

public static function outconvGlobal(Context $context): Value
{
self::ensureOutconvGlobal($context);
$g = $context->module->getNamedGlobal(self::G_OUTCONV_ENABLED);
if (null === $g) {
throw new \LogicException(self::G_OUTCONV_ENABLED.' missing (#20014)');
}

return $g;
}

private static function ensureOutconvGlobal(Context $context): void
{
if (null !== $context->module->getNamedGlobal(self::G_OUTCONV_ENABLED)) {
return;
}
$i64 = $context->getTypeFromString('int64');
$g = $context->module->addGlobal($i64, self::G_OUTCONV_ENABLED);
$g->setInitializer($i64->constInt(0, false));
}

private static function ensureJitHelperCompiled(Context $context): void
{
JitVmHelperLink::ensureCompiled(
$context,
self::HELPER_PATH,
self::COMPILED_HELPERS,
'mb_output_handler',
true
);
}
}
17 changes: 17 additions & 0 deletions test/repro/aot_mb_output_handler_runtime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

/**
* AOT: mb_output_handler() with runtime string arg (#20014 leftover).
* php-src: ext/mbstring/mbstring.c PHP_FUNCTION(mb_output_handler)
*/
mb_internal_encoding('UTF-8');
mb_http_output('ISO-8859-1');

$s = "caf\xC3\xA9";
// Literal 9 === PHP_OUTPUT_HANDLER_START|END (status may be runtime in OB callbacks; string was the AOT blocker).
echo 'rt=', bin2hex(mb_output_handler($s, 9)), "\n";

mb_http_output('pass');
echo 'pass=', bin2hex(mb_output_handler($s, 9)), "\n";
3 changes: 3 additions & 0 deletions test/selfhost/compiler_lib_spine_smoke/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,7 @@
require_once __DIR__.'/../../../ext/mbstring/JitMbLanguage.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbSubstituteCharacter.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbGetInfo.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbOutputHandler.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbHttpInput.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbHttpOutput.php';
require_once __DIR__.'/../../../ext/mbstring/JitMbConvertCase.php';
Expand Down Expand Up @@ -1373,6 +1374,7 @@
require_once __DIR__.'/../../../ext/mbstring/MbPreferredMimeNameJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbEncodingAliasesJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbGetInfoJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbOutputHandlerJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbScrubJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbStrlenJitHelper.php';
require_once __DIR__.'/../../../ext/mbstring/MbTrimJitHelper.php';
Expand Down Expand Up @@ -8051,6 +8053,7 @@
require_once __DIR__.'/../../../lib/JIT/Builtin/MbLanguageRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbSubstituteCharacterRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbGetInfoRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbOutputHandlerRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbHttpInputRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbHttpOutputRuntime.php';
require_once __DIR__.'/../../../lib/JIT/Builtin/MbChrOrdRuntime.php';
Expand Down
Loading
Loading