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
52 changes: 15 additions & 37 deletions ext/standard/JitPrintf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
147 changes: 116 additions & 31 deletions ext/standard/JitSprintf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions lib/JIT/Builtin/SprintfSnprintfRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -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__'];
Expand All @@ -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');
Expand Down
5 changes: 5 additions & 0 deletions lib/JIT/Builtin/StringFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -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__*');
Expand Down
Loading