diff --git a/ext/standard/JitPrintf.php b/ext/standard/JitPrintf.php index 92dadae494e..02d59ddab19 100644 --- a/ext/standard/JitPrintf.php +++ b/ext/standard/JitPrintf.php @@ -58,46 +58,24 @@ public static function format(Context $context, JITVariable ...$args): Value ); } - $valueTy = $context->getTypeFromString('__value__'); - $i32 = $context->getTypeFromString('int32'); + // Multi-arg: format via JitSprintf (direct snprintf), then echo the result. + \PHPCompiler\JIT\Builtin\StringFormat::ensureRuntimeHelpersPublic($context); + $formatted = JitSprintf::formatWithFmt($context, $fmt, ...array_slice($args, 1)); + $i64 = $context->getTypeFromString('int64'); + $i8p = $context->getTypeFromString('int8*'); $sizeT = $context->getTypeFromString('size_t'); - $elemSize = $context->builder->ptrToInt( - $context->builder->gep( - $valueTy->pointerType(0)->constNull(), - $i32->constInt(1, false) - ), - $sizeT - ); - $argvCountSize = $context->builder->intCast( - $i64->constInt($numArgs, false), - $sizeT - ); - $argvBytes = $context->builder->mul($elemSize, $argvCountSize); - $argvRaw = $context->builder->call( - $context->lookupFunction('__mm__malloc'), - $argvBytes - ); - $argvPtr = $context->builder->pointerCast( - $argvRaw, - $context->getTypeFromString('__value__*') - ); - for ($i = 0; $i < $numArgs; ++$i) { - $slot = $context->builder->inBoundsGEP( - $argvPtr, - $i64->constInt($i, false) - ); - JitSprintf::writeArg($context, $slot, $args[$i + 1]); - } - $argcVal = $i64->constInt($numArgs, false); - $written = $context->builder->call( - $context->lookupFunction('__compiler_printf'), - $fmt, - $argcVal, - $argvPtr + $strPtr = $context->getTypeFromString('__string__*'); + $stringMap = $context->structFieldMap['__string__']; + + $data = $context->builder->structGep($formatted, $stringMap['value']); + $len = $context->builder->load($context->builder->structGep($formatted, $stringMap['length'])); + $context->builder->call( + $context->lookupFunction('__phpc_ob_echo_substr'), + $data, + $len ); - $context->builder->call($context->lookupFunction('__mm__free'), $argvRaw); - return $context->builder->intCast($written, $i64); + return $context->builder->zExt($len, $i64); } } diff --git a/ext/standard/JitSprintf.php b/ext/standard/JitSprintf.php index 4fc34d4c146..4a88abd3172 100644 --- a/ext/standard/JitSprintf.php +++ b/ext/standard/JitSprintf.php @@ -57,49 +57,134 @@ public static function format(Context $context, JITVariable ...$args): Value ); } - $valueTy = $context->getTypeFromString('__value__'); - $i32 = $context->getTypeFromString('int32'); + // Multi-arg: direct libc snprintf — bypasses broken NestedJIT pack path. + // At compile time we know arg count and types, so we emit a single + // snprintf(buf, size, fmt_nul, typed1, typed2, ...) call. + \PHPCompiler\JIT\LibcExtern::ensureSnprintf($context); + $i64 = $context->getTypeFromString('int64'); + $i8p = $context->getTypeFromString('int8*'); $sizeT = $context->getTypeFromString('size_t'); - $elemSize = $context->builder->ptrToInt( - $context->builder->gep( - $valueTy->pointerType(0)->constNull(), - $i32->constInt(1, false) - ), - $sizeT - ); - $argvCountSize = $context->builder->intCast( - $i64->constInt($numArgs, false), - $sizeT - ); - $argvBytes = $context->builder->mul($elemSize, $argvCountSize); - $argvRaw = $context->builder->call( + $charPtr = $context->getTypeFromString('char*'); + $strPtr = $context->getTypeFromString('__string__*'); + + $fmtNul = \PHPCompiler\JIT\Builtin\SprintfSnprintfRuntime::nullTerminatedCopyPublic($context, $fmt); + + $bufSize = 1024; + $outBuf = $context->builder->call( $context->lookupFunction('__mm__malloc'), - $argvBytes - ); - $argvPtr = $context->builder->pointerCast( - $argvRaw, - $context->getTypeFromString('__value__*') + $sizeT->constInt($bufSize, false) ); + $outChar = $context->builder->pointerCast($outBuf, $charPtr); + $snprintfArgs = [ + $outChar, + $sizeT->constInt($bufSize, false), + $fmtNul, + ]; + $toFree = []; for ($i = 0; $i < $numArgs; ++$i) { - $slot = $context->builder->inBoundsGEP( - $argvPtr, - $i64->constInt($i, false) - ); - self::writeArg($context, $slot, $args[$i + 1]); + $extracted = self::extractSnprintfArg($context, $args[$i + 1], $toFree); + $snprintfArgs[] = $extracted; } - $argcVal = $i64->constInt($numArgs, false); + + $written = $context->builder->call( + $context->lookupFunction('snprintf'), + ...$snprintfArgs + ); + + $len = $context->builder->zExt($written, $i64); $result = $context->builder->call( - $context->lookupFunction('__compiler_sprintf'), - $fmt, - $argcVal, - $argvPtr + $context->lookupFunction('__string__init'), + $len, + $context->builder->pointerCast($outBuf, $i8p) ); - $context->builder->call($context->lookupFunction('__mm__free'), $argvRaw); + $context->builder->call($context->lookupFunction('__mm__free'), $outBuf); + $context->builder->call($context->lookupFunction('__mm__free'), $fmtNul); + foreach ($toFree as $ptr) { + $context->builder->call($context->lookupFunction('__mm__free'), $ptr); + } return $result; } + /** + * Extract a typed value from a JIT variable for use as a snprintf vararg. + * + * Returns i64 for longs/bools, double for floats, char* for strings. + * String args are NUL-terminated into a malloc'd buffer tracked in $toFree. + * + * @param Value[] $toFree collects malloc'd buffers to free after snprintf + */ + private static function extractSnprintfArg(Context $context, JITVariable $arg, array &$toFree): Value + { + // When inference says native type but storage is __value__*, read from the box. + if (null === $arg->valueBoxAliasPtr + && \in_array($arg->type, [ + JITVariable::TYPE_NATIVE_LONG, + JITVariable::TYPE_NATIVE_DOUBLE, + JITVariable::TYPE_NATIVE_BOOL, + JITVariable::TYPE_STRING, + ], true) + && \in_array( + $context->getStringFromType($arg->value->typeOf()), + ['__value__*', '__value__value*'], + true + )) { + $valuePtr = JitValueBox::normalizeValuePtr($context, $arg->value); + switch ($arg->type) { + case JITVariable::TYPE_NATIVE_DOUBLE: + return $context->builder->call( + $context->lookupFunction('__value__readDouble'), + $valuePtr + ); + case JITVariable::TYPE_STRING: + $strVal = $context->builder->call( + $context->lookupFunction('__value__readString'), + $valuePtr + ); + $strSep = $context->builder->call( + $context->lookupFunction('__string__separate'), + $strVal + ); + $nul = \PHPCompiler\JIT\Builtin\SprintfSnprintfRuntime::nullTerminatedCopyPublic($context, $strSep); + $toFree[] = $nul; + + return $nul; + default: + return $context->builder->call( + $context->lookupFunction('__value__readLong'), + $valuePtr + ); + } + } + switch ($arg->type) { + case JITVariable::TYPE_NATIVE_LONG: + case JITVariable::TYPE_NATIVE_BOOL: + return $context->helper->loadValue($arg); + case JITVariable::TYPE_NATIVE_DOUBLE: + return $context->helper->loadValue($arg); + case JITVariable::TYPE_STRING: + $str = $context->helper->loadValue($arg); + $owned = $context->builder->call( + $context->lookupFunction('__string__separate'), + $str + ); + $nul = \PHPCompiler\JIT\Builtin\SprintfSnprintfRuntime::nullTerminatedCopyPublic($context, $owned); + $toFree[] = $nul; + + return $nul; + case JITVariable::TYPE_VALUE: + $valuePtr = JitValueBox::valuePtrFromVariable($context, $arg); + + return $context->builder->call( + $context->lookupFunction('__value__readLong'), + $valuePtr + ); + default: + return $context->helper->loadValue($arg); + } + } + public static function writeArg(Context $context, Value $slot, JITVariable $arg): void { $ptr = JitValueBox::pointer($context, $slot); diff --git a/lib/JIT/Builtin/SprintfSnprintfRuntime.php b/lib/JIT/Builtin/SprintfSnprintfRuntime.php index c50663132ca..52d98ef63c8 100644 --- a/lib/JIT/Builtin/SprintfSnprintfRuntime.php +++ b/lib/JIT/Builtin/SprintfSnprintfRuntime.php @@ -162,6 +162,11 @@ public static function formatOneArg( return $result; } + public static function nullTerminatedCopyPublic(Context $context, Value $strSep): Value + { + return self::nullTerminatedCopy($context, $strSep); + } + private static function nullTerminatedCopy(Context $context, Value $strSep): Value { $stringMap = $context->structFieldMap['__string__']; @@ -188,6 +193,11 @@ private static function nullTerminatedCopy(Context $context, Value $strSep): Val return $context->builder->pointerCast($buf, $charPtr); } + public static function ensureDeclsPublic(Context $context): void + { + self::ensureDecls($context); + } + private static function ensureDecls(Context $context): void { $double = $context->getTypeFromString('double'); diff --git a/lib/JIT/Builtin/StringFormat.php b/lib/JIT/Builtin/StringFormat.php index a883e5ab6c4..d26e872d5dc 100644 --- a/lib/JIT/Builtin/StringFormat.php +++ b/lib/JIT/Builtin/StringFormat.php @@ -270,6 +270,11 @@ private static function implementNumberFormatBridge(Context $context): void $context->registerFunction($abiName, $fn); } + public static function ensureRuntimeHelpersPublic(Context $context): void + { + self::ensureRuntimeHelpers($context); + } + private static function ensureRuntimeHelpers(Context $context): void { $strPtr = $context->getTypeFromString('__string__*');