diff --git a/ext/mbstring/JitMbStrlen.php b/ext/mbstring/JitMbStrlen.php index 606ff3ab1fb..5a487d9c611 100644 --- a/ext/mbstring/JitMbStrlen.php +++ b/ext/mbstring/JitMbStrlen.php @@ -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; @@ -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); } } diff --git a/ext/mbstring/mb_strlen.php b/ext/mbstring/mb_strlen.php index 1203311cf5a..b8ea34a2c74 100644 --- a/ext/mbstring/mb_strlen.php +++ b/ext/mbstring/mb_strlen.php @@ -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; @@ -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)); } @@ -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) ); } diff --git a/test/compliance/MbStrlenJITTest.php b/test/compliance/MbStrlenJITTest.php new file mode 100644 index 00000000000..b95831dddc2 --- /dev/null +++ b/test/compliance/MbStrlenJITTest.php @@ -0,0 +1,38 @@ + 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.' + ); + } + } +} diff --git a/test/compliance/MbStrlenVMTest.php b/test/compliance/MbStrlenVMTest.php new file mode 100644 index 00000000000..513bccc81d4 --- /dev/null +++ b/test/compliance/MbStrlenVMTest.php @@ -0,0 +1,30 @@ + 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'); + } +} diff --git a/test/compliance/cases/stdlib/mb_strlen_enum_typeerror.phpt b/test/compliance/cases/stdlib/mb_strlen_enum_typeerror.phpt new file mode 100644 index 00000000000..638d55bfab2 --- /dev/null +++ b/test/compliance/cases/stdlib/mb_strlen_enum_typeerror.phpt @@ -0,0 +1,13 @@ +--TEST-- +stdlib mb_strlen() — backed enum case TypeError (#5873, ext/mbstring/mbstring.c) +--FILE-- +getMessage(), "\n"; +} +--EXPECT-- +mb_strlen(): Argument #1 ($string) must be of type string, Es given diff --git a/test/compliance/cases/stdlib/mb_strlen_enum_typeerror_jit.phpt b/test/compliance/cases/stdlib/mb_strlen_enum_typeerror_jit.phpt new file mode 100644 index 00000000000..2d778891896 --- /dev/null +++ b/test/compliance/cases/stdlib/mb_strlen_enum_typeerror_jit.phpt @@ -0,0 +1,13 @@ +--TEST-- +stdlib mb_strlen() JIT — backed enum case TypeError (#5873) +--FILE-- +getMessage(), "\n"; +} +--EXPECT-- +mb_strlen(): Argument #1 ($string) must be of type string, Es given diff --git a/test/fixtures/aot/cases/mb_strlen_enum_typeerror.phpt b/test/fixtures/aot/cases/mb_strlen_enum_typeerror.phpt new file mode 100644 index 00000000000..84311edb631 --- /dev/null +++ b/test/fixtures/aot/cases/mb_strlen_enum_typeerror.phpt @@ -0,0 +1,13 @@ +--TEST-- +AOT: mb_strlen() — backed enum case TypeError (#5873) +--FILE-- +getMessage(), "\n"; +} +--EXPECT-- +mb_strlen(): Argument #1 ($string) must be of type string, Es given diff --git a/test/repro/maintainer_mb_strlen_enum.php b/test/repro/maintainer_mb_strlen_enum.php new file mode 100644 index 00000000000..f1c49769814 --- /dev/null +++ b/test/repro/maintainer_mb_strlen_enum.php @@ -0,0 +1,7 @@ +getMessage(); +}