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
28 changes: 15 additions & 13 deletions ext/mbstring/JitMbStrlen.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPCompiler\ext\standard\VmString;
use PHPCompiler\JIT\Builtin\StringUtf8Strlen;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\Variable as JITVariable;
use PHPLLVM\Value;

Expand All @@ -15,26 +16,27 @@
*/
final class JitMbStrlen
{
public static function utf8Length(Context $context, JITVariable $arg): Value
public static function utf8LengthFromPtr(Context $context, Value $strPtr): Value
{
if (JITVariable::TYPE_STRING !== $arg->type) {
throw new \LogicException('mb_strlen() only supports strings in this compiler build');
}
StringUtf8Strlen::ensureLinked($context);

return $context->builder->call(
$context->lookupFunction('__compiler_utf8_strlen'),
$strPtr
);
}

$literal = $arg->compileTimeString ?? null;
if (null !== $literal) {
public static function utf8Length(Context $context, JITVariable $arg): Value
{
if (JITVariable::TYPE_STRING === $arg->type && null !== ($arg->compileTimeString ?? null)) {
return $context->constantFromInteger(
VmString::utf8CharLength($literal),
VmString::utf8CharLength($arg->compileTimeString),
'int64'
);
}

StringUtf8Strlen::ensureLinked($context);
$strPtr = $context->helper->loadValue($arg);
$str = JitStringBuiltinArg::lower($context, $arg, 'mb_strlen', 0, 'string');

return $context->builder->call(
$context->lookupFunction('__compiler_utf8_strlen'),
$strPtr
);
return self::utf8LengthFromPtr($context, $str);
}
}
45 changes: 27 additions & 18 deletions ext/mbstring/mb_strlen.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPCompiler\Frame;
use PHPCompiler\Func\Internal;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\JitStringBuiltinArg;
use PHPCompiler\JIT\Variable;
use PHPCompiler\VM\Variable as VMVariable;
use PHPLLVM\Value;
Expand All @@ -30,21 +31,23 @@ public function execute(Frame $frame): void
if ($argc < 1 || $argc > 2) {
throw new \LogicException('mb_strlen() requires one or two arguments');
}
$strVar = $frame->calledArgs[0]->resolveIndirect();
if (VMVariable::TYPE_STRING !== $strVar->type) {
throw new \LogicException('mb_strlen() only supports strings in this compiler build');
}
$str = VmString::coerceStringBuiltinArg(
$frame->calledArgs[0],
'mb_strlen',
0,
'string'
);
if (null === $frame->returnVar) {
return;
}
$str = $strVar->toString();
$encoding = 'UTF-8';
if (2 === $argc) {
$encVar = $frame->calledArgs[1]->resolveIndirect();
if (VMVariable::TYPE_STRING !== $encVar->type) {
throw new \LogicException('mb_strlen() encoding must be a string in this compiler build');
}
$encoding = $encVar->toString();
$encoding = VmString::coerceStringBuiltinArg(
$frame->calledArgs[1],
'mb_strlen',
1,
'encoding'
);
}
$frame->returnVar->int(self::lengthForEncoding($str, $encoding));
}
Expand All @@ -55,29 +58,35 @@ public function call(Context $context, Variable ...$args): Value
if ($argc < 1 || $argc > 2) {
throw new \LogicException('mb_strlen() requires one or two arguments');
}
if (1 === $argc && Variable::TYPE_STRING === $args[0]->type && null !== ($args[0]->compileTimeString ?? null)) {
return $context->constantFromInteger(
VmString::utf8CharLength($args[0]->compileTimeString),
'int64'
);
}

$str = JitStringBuiltinArg::lower($context, $args[0], 'mb_strlen', 0, 'string');

if (1 === $argc) {
return JitMbStrlen::utf8Length($context, $args[0]);
return JitMbStrlen::utf8LengthFromPtr($context, $str);
}
if (Variable::TYPE_STRING !== $args[1]->type) {
throw new \LogicException('mb_strlen() encoding must be a string in this compiler build');
}
$encoding = $args[1]->compileTimeString ?? null;
if ('UTF-8' === $encoding) {
return JitMbStrlen::utf8Length($context, $args[0]);
return JitMbStrlen::utf8LengthFromPtr($context, $str);
}
if (null !== $encoding && 'ASCII' !== $encoding && '8BIT' !== $encoding) {
throw new \LogicException(
'mb_strlen() JIT only supports UTF-8, ASCII, or 8BIT encoding literals in this compiler build'
);
}
if (Variable::TYPE_STRING !== $args[0]->type) {
throw new \LogicException('mb_strlen() only supports strings in this compiler build');
}
$argValue = $context->helper->loadValue($args[0]);
$offset = $context->structFieldIndex($argValue, 'length');

$offset = $context->structFieldIndex($str, 'length');

return $context->builder->load(
$context->builder->structGep($argValue, $offset)
$context->builder->structGep($str, $offset)
);
}

Expand Down
38 changes: 38 additions & 0 deletions test/compliance/MbStrlenJITTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

require_once __DIR__.'/../BaseTest.php';

/**
* @group llvm
*/
/** JIT compliance for mb_strlen(). */
final class MbStrlenJITTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'mb_strlen_jit.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/mb_strlen_jit.phpt',
'mb_strlen_jit.phpt'
);
yield 'mb_strlen_enum_typeerror_jit.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/mb_strlen_enum_typeerror_jit.phpt',
'mb_strlen_enum_typeerror_jit.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/jit.php');
if (!LlvmToolchain::hasLibrary(dirname(__DIR__, 2))) {
$this->markTestSkipped(
'LLVM 9 toolchain not available. Run script/install-llvm9.sh or use the 22.04-dev Docker image.'
);
}
}
}
30 changes: 30 additions & 0 deletions test/compliance/MbStrlenVMTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

require_once __DIR__.'/../BaseTest.php';

/** VM compliance for mb_strlen(). */
final class MbStrlenVMTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'mb_strlen.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/mb_strlen.phpt',
'mb_strlen.phpt'
);
yield 'mb_strlen_enum_typeerror.phpt' => self::parsePHPT(
__DIR__.'/cases/stdlib/mb_strlen_enum_typeerror.phpt',
'mb_strlen_enum_typeerror.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/vm.php');
}
}
13 changes: 13 additions & 0 deletions test/compliance/cases/stdlib/mb_strlen_enum_typeerror.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
stdlib mb_strlen() — backed enum case TypeError (#5873, ext/mbstring/mbstring.c)
--FILE--
<?php
enum Es: string { case B = 'hi'; }
try {
mb_strlen(Es::B);
echo "uncaught\n";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
--EXPECT--
mb_strlen(): Argument #1 ($string) must be of type string, Es given
13 changes: 13 additions & 0 deletions test/compliance/cases/stdlib/mb_strlen_enum_typeerror_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
stdlib mb_strlen() JIT — backed enum case TypeError (#5873)
--FILE--
<?php
enum Es: string { case B = 'hi'; }
try {
mb_strlen(Es::B);
echo "uncaught\n";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
--EXPECT--
mb_strlen(): Argument #1 ($string) must be of type string, Es given
13 changes: 13 additions & 0 deletions test/fixtures/aot/cases/mb_strlen_enum_typeerror.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
AOT: mb_strlen() — backed enum case TypeError (#5873)
--FILE--
<?php
enum Es: string { case B = 'hi'; }
try {
mb_strlen(Es::B);
echo "uncaught\n";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
--EXPECT--
mb_strlen(): Argument #1 ($string) must be of type string, Es given
7 changes: 7 additions & 0 deletions test/repro/maintainer_mb_strlen_enum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
enum Es: string { case B = 'hi'; }
try {
echo mb_strlen(Es::B);
} catch (Throwable $e) {
echo get_class($e).': '.$e->getMessage();
}