diff --git a/ext/standard/strval.php b/ext/standard/strval.php index 1b48326b00a..4f227fb1228 100644 --- a/ext/standard/strval.php +++ b/ext/standard/strval.php @@ -21,6 +21,7 @@ use PHPCompiler\VM; use PHPCompiler\VM\ErrorReporter; use PHPCompiler\VM\Variable; +use PHPCompiler\VM\VmResourceIdString; use PHPLLVM\Builder; use PHPLLVM\Value; @@ -68,7 +69,8 @@ public function call(Context $context, JITVariable ...$args): Value case JITVariable::TYPE_NATIVE_LONG: return JitResourceIdString::formatNativeLong( $context, - $context->helper->loadValue($args[0]) + $context->helper->loadValue($args[0]), + null ); case JITVariable::TYPE_NATIVE_DOUBLE: return \PHPCompiler\JIT\Builtin\ZendDoubleStringRuntime::format( @@ -123,7 +125,7 @@ public function valueToString(Context $context, Value $valuePtr): Value ); $context->builder->positionAtEnd($longBlock); - $longStr = JitResourceIdString::formatNativeLong( + $longStr = VmResourceIdString::formatBoxedNativeLong( $context, $context->builder->call($context->lookupFunction('__value__readLong'), $valuePtr) ); diff --git a/lib/JIT.php b/lib/JIT.php index 09dba7ea2d6..d2ab10748ae 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -8388,7 +8388,12 @@ private function compileBlockInternal( JIT\StringOffsetHelper::emitAssignOpError($this->context); break; } - $newVal = $this->compileConcatIntoNewString($left, $right); + $newVal = $this->compileConcatIntoNewString( + $left, + $right, + $block->getOperand($op->arg2), + $block->getOperand($op->arg3) + ); $this->assignOperand($destOp, $newVal, true); $this->maybeRefreshIncludeBindingsBeforeUse(); break; @@ -8455,7 +8460,12 @@ private function compileBlockInternal( if (null !== $result->objectPropertySlot) { $this->compileObjectPropertyConcatOp($result, $left, $right); } elseif (Variable::TYPE_VALUE === $result->type || JIT\JitValueBox::isValueOperand($result)) { - $newVal = $this->compileConcatIntoNewString($left, $right); + $newVal = $this->compileConcatIntoNewString( + $left, + $right, + $block->getOperand($op->arg2), + $block->getOperand($op->arg3) + ); JIT\JitValueBox::assignToPointer( $this->context, $this->valueBoxPointer($result), @@ -8472,10 +8482,10 @@ private function compileBlockInternal( // Fresh and in-place native concat: JitStringConcat + store. Avoid // string->concat __string__realloc on entry allocas (AOT strlen→0, #15642). $leftVar = $this->context->helper->loadValue( - JIT\JitNativeString::coerce($this->context, $left) + JIT\JitNativeString::coerce($this->context, $left, $block->getOperand($op->arg2)) ); $rightVar = $this->context->helper->loadValue( - JIT\JitNativeString::coerce($this->context, $right) + JIT\JitNativeString::coerce($this->context, $right, $block->getOperand($op->arg3)) ); $newStr = \PHPCompiler\ext\standard\JitStringConcat::concat( $this->context, @@ -9056,7 +9066,8 @@ private function compileBlockInternal( case Variable::TYPE_NATIVE_LONG: JIT\ValueEchoHelper::echoNativeLong( $this->context, - $this->context->helper->loadValue($arg) + $this->context->helper->loadValue($arg), + $echoOp ); break; case Variable::TYPE_NATIVE_DOUBLE: @@ -15661,11 +15672,16 @@ function () use ($dest, $newVal): void { } /** Allocate a fresh native string holding left . right (php-src string concat semantics). */ - private function compileConcatIntoNewString(Variable $left, Variable $right): Variable + private function compileConcatIntoNewString( + Variable $left, + Variable $right, + ?\PHPCfg\Operand $leftOp = null, + ?\PHPCfg\Operand $rightOp = null + ): Variable { $this->context->intrinsic->builder = $this->context->builder; - $left = JIT\JitNativeString::coerce($this->context, $left); - $right = JIT\JitNativeString::coerce($this->context, $right); + $left = JIT\JitNativeString::coerce($this->context, $left, $leftOp); + $right = JIT\JitNativeString::coerce($this->context, $right, $rightOp); $leftVar = $this->context->helper->loadValue($left); $rightVar = $this->context->helper->loadValue($right); $map = $this->context->structFieldMap['__string__']; diff --git a/lib/JIT/IncDecResourceProvenance.php b/lib/JIT/IncDecResourceProvenance.php index f2d78d37264..1c3e337816c 100644 --- a/lib/JIT/IncDecResourceProvenance.php +++ b/lib/JIT/IncDecResourceProvenance.php @@ -30,6 +30,8 @@ * The analysis is deliberately conservative: anything it does not recognise — a call, a parameter, * a property or array read — answers "unknown" and keeps the guard. Only the listed * value-producing ops, which cannot yield a resource under any input, allow it to be dropped. + * + * Also used to skip resource-handle checks in int→string lowering (#23811). */ final class IncDecResourceProvenance { @@ -93,6 +95,19 @@ public static function cannotBeResource(?Operand $op): bool return self::operandIsSafe($op, $seen, $budget); } + /** + * Like {@see cannotBeResource()} but peels php-cfg {@see Operand\Temporary} wrappers first. + * Used for int→string lowering where concat operands are often temporaries (#23811). + */ + public static function cannotBeResourceForString(?Operand $op): bool + { + while ($op instanceof Operand\Temporary && $op->original instanceof Operand) { + $op = $op->original; + } + + return self::cannotBeResource($op); + } + /** * @param array $seen */ diff --git a/lib/JIT/JitNativeString.php b/lib/JIT/JitNativeString.php index 679c299bc00..45df4eee5ed 100644 --- a/lib/JIT/JitNativeString.php +++ b/lib/JIT/JitNativeString.php @@ -8,6 +8,7 @@ namespace PHPCompiler\JIT; +use PHPCfg\Operand; use PHPLLVM\Builder; use PHPLLVM\Value; @@ -26,7 +27,7 @@ public static function ensureInsertBlock(Context $context): void $context->builder->positionAtEnd($resume); } - public static function coerce(Context $context, Variable $var): Variable + public static function coerce(Context $context, Variable $var, ?Operand $sourceOperand = null): Variable { if (Variable::TYPE_STRING === $var->type) { return $var; @@ -80,7 +81,7 @@ public static function coerce(Context $context, Variable $var): Variable $context, Variable::TYPE_STRING, Variable::KIND_VALUE, - JitResourceIdString::formatNativeLong($context, $value) + JitResourceIdString::formatNativeLong($context, $value, $sourceOperand) ); case Variable::TYPE_NATIVE_DOUBLE: // PG(precision) via VmZendDoubleString (#21963, Zend/zend_operators.c). diff --git a/lib/JIT/JitResourceIdString.php b/lib/JIT/JitResourceIdString.php index 3f0ded26716..c6305aba198 100644 --- a/lib/JIT/JitResourceIdString.php +++ b/lib/JIT/JitResourceIdString.php @@ -5,6 +5,7 @@ namespace PHPCompiler\JIT; use PHPCompiler\VM\VmResourceIdString; +use PHPCfg\Operand; use PHPLLVM\Value; /** @@ -14,8 +15,12 @@ */ final class JitResourceIdString { - public static function formatNativeLong(Context $context, Value $longVal): Value + public static function formatNativeLong( + Context $context, + Value $longVal, + ?Operand $sourceOperand = null + ): Value { - return VmResourceIdString::formatNativeLong($context, $longVal); + return VmResourceIdString::formatNativeLong($context, $longVal, $sourceOperand); } } diff --git a/lib/JIT/ValueEchoHelper.php b/lib/JIT/ValueEchoHelper.php index 55e1fcda96f..0f943b86e3c 100644 --- a/lib/JIT/ValueEchoHelper.php +++ b/lib/JIT/ValueEchoHelper.php @@ -12,6 +12,8 @@ namespace PHPCompiler\JIT; use PHPCompiler\JIT\Builtin\ValueEchoRuntime; +use PHPCompiler\JIT\IncDecResourceProvenance; +use PHPCfg\Operand; use PHPCompiler\VM\ValueEchoSupport; use PHPLLVM\Value; @@ -34,12 +36,24 @@ public static function echoLiteral(Context $context, string $literal): void /** * Echo a native long, formatting stream/dir resources like Zend (ext/standard, #4740). */ - public static function echoNativeLong(Context $context, Value $longVal): void + public static function echoNativeLong( + Context $context, + Value $longVal, + ?Operand $sourceOperand = null + ): void { Builtin\StringDir::ensureLinked($context); $tag = 'enl'.(string) ++self::$seq; $i64 = $context->getTypeFromString('int64'); $handle = $context->builder->zExt($longVal, $i64); + if (IncDecResourceProvenance::cannotBeResourceForString($sourceOperand)) { + $context->builder->call( + $context->lookupFunction('__phpc_ob_echo_ll'), + $handle + ); + + return; + } $isRes = JitValueCompare::nativeLongIsResource($context, $handle); $plainBlock = BasicBlockHelper::append($context, 'echo_native_long_plain_'.$tag); diff --git a/lib/VM/VmResourceIdString.php b/lib/VM/VmResourceIdString.php index e6621dd13d5..047e020602b 100644 --- a/lib/VM/VmResourceIdString.php +++ b/lib/VM/VmResourceIdString.php @@ -7,6 +7,8 @@ use PHPCompiler\JIT\BasicBlockHelper; use PHPCompiler\JIT\Builtin\StringDir; use PHPCompiler\JIT\Context; +use PHPCompiler\JIT\IncDecResourceProvenance; +use PHPCfg\Operand; use PHPCompiler\VM\ValueEchoSupport; use PHPLLVM\Value; @@ -18,13 +20,20 @@ */ final class VmResourceIdString { - public static function formatNativeLong(Context $context, Value $longVal): Value + public static function formatNativeLong( + Context $context, + Value $longVal, + ?Operand $sourceOperand = null + ): Value { StringDir::ensureLinked($context); $i64 = $context->getTypeFromString('int64'); $handle = $longVal->typeOf() === $i64 ? $longVal : $context->builder->zExt($longVal, $i64); + if (IncDecResourceProvenance::cannotBeResourceForString($sourceOperand)) { + return self::snprintf($context, $handle, '%lld'); + } $isRes = VmValueCompare::nativeLongIsResource($context, $handle); $tag = 'resid_'.(string) spl_object_id($context); @@ -53,6 +62,17 @@ public static function formatNativeLong(Context $context, Value $longVal): Value return $phi; } + /** Boxed {@see __value__} TYPE_NATIVE_LONG — never a resource handle (#23811). */ + public static function formatBoxedNativeLong(Context $context, Value $longVal): Value + { + $i64 = $context->getTypeFromString('int64'); + $handle = $longVal->typeOf() === $i64 + ? $longVal + : $context->builder->zExt($longVal, $i64); + + return self::snprintf($context, $handle, '%lld'); + } + private static function snprintf(Context $context, Value $handle, string $format): Value { $sizeT = $context->getTypeFromString('size_t'); diff --git a/test/differential/cases/g07_incdec_resource_provenance.php b/test/differential/cases/g07_incdec_resource_provenance.php index c80f01aecb0..397f4b3c7fd 100644 --- a/test/differential/cases/g07_incdec_resource_provenance.php +++ b/test/differential/cases/g07_incdec_resource_provenance.php @@ -1,10 +1,7 @@