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
3 changes: 3 additions & 0 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -11275,6 +11275,8 @@ private function compileBlockInternal(
);
break;
case Variable::TYPE_STRING:
// Lazy ob_* — bare __phpc_ob_echo_* lookups below (#34695).
JIT\Builtin\ObOutputRuntime::ensureLinked($this->context);
if ($arg->kind === Variable::KIND_VALUE
&& 'i8*' === $this->context->getStringFromType($arg->value->typeOf())
) {
Expand Down Expand Up @@ -11329,6 +11331,7 @@ private function compileBlockInternal(
);
break;
case Variable::TYPE_NATIVE_BOOL:
JIT\Builtin\ObOutputRuntime::ensureLinked($this->context);
$boolVal = $this->context->helper->loadValue($arg);
$charPtr = $this->context->getTypeFromString('char*');
$trueBlock = JIT\BasicBlockHelper::append($this->context, 'echo_bool_true');
Expand Down
8 changes: 6 additions & 2 deletions lib/JIT/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -2317,7 +2317,10 @@ private function ensureMinimalUserStandaloneBodies(): void
// JitReturnPending always-on removed (#34621): TryCatchHelper / emitPendingReturnResume
// already ensureLinked before lookup (peer #34612). JitHelperAbiBridge restores insert
// mid-{main}. Leftover Context NestedJIT vs Runtime ABI drift mints *.1 (#31894 / #32122).
Builtin\ObOutputRuntime::ensureLinked($this);
// ObOutput always-on removed (#34695): ValueEchoHelper / ValueEchoRuntime /
// StringVarDump / ObOutput / StreamReadRuntime already ensureLinked before
// __phpc_ob_echo_* lookup (peer #34642). Leftover Context NestedJIT vs Runtime ABI
// drift mints ob_*.1 (#31894 / #32122). Thin hello-world must not NestedJIT ob during init.
// StringRandomBytes / Utf8Latin1 / RewriteVars / Define / StrContains / StatPath /
// FileGetContents / MetaTags / HashCrypto / MbNumericEntity / Readfile / Bin2hex /
// Addslashes / Stripslashes / FilePutContents / IniRuntime always-on removed (#34578):
Expand Down Expand Up @@ -2367,7 +2370,8 @@ private function ensureFullStandaloneBodies(): void
Builtin\AssertFail::ensureStandaloneBodies($this);
Builtin\AssertOptionsRuntime::ensureStandaloneBodies($this);
Builtin\JitReturnPending::ensureStandaloneBodies($this);
Builtin\ObOutputRuntime::ensureLinked($this);
// ObOutput always-on removed (#34695): ValueEchoRuntime::ensureLinked → ObOutput
// (and ValueEchoHelper call sites). Do not re-add before ValueEcho here.
Builtin\ValueEchoRuntime::ensureLinked($this);
Builtin\CliArgvRuntime::ensureStandaloneBodies($this);
// Nested-JIT string helpers: lazy via ensureLinked during spine/thin init (#14472, #20571).
Expand Down
5 changes: 5 additions & 0 deletions lib/JIT/ValueEchoHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace PHPCompiler\JIT;

use PHPCompiler\JIT\Builtin\ObOutputRuntime;
use PHPCompiler\JIT\Builtin\ValueEchoRuntime;
use PHPCompiler\JIT\IncDecResourceProvenance;
use PHPCfg\Operand;
Expand All @@ -23,6 +24,8 @@ final class ValueEchoHelper

public static function echoLiteral(Context $context, string $literal): void
{
// Lazy ob_* ABI — Context ensureMinimal must not NestedJIT ObOutput (#34695 / peer #34642).
ObOutputRuntime::ensureLinked($context);
$charPtr = $context->getTypeFromString('char*');
$context->builder->call(
$context->lookupFunction('__phpc_ob_echo_cstr'),
Expand All @@ -42,6 +45,7 @@ public static function echoNativeLong(
?Operand $sourceOperand = null
): void
{
ObOutputRuntime::ensureLinked($context);
Builtin\StringDir::ensureLinked($context);
$tag = 'enl'.(string) ++self::$seq;
$i64 = $context->getTypeFromString('int64');
Expand Down Expand Up @@ -189,6 +193,7 @@ public static function echoObjectVariable(Context $context, Variable $objectVar,

public static function echoStringVariable(Context $context, Variable $stringVar): void
{
ObOutputRuntime::ensureLinked($context);
$argValue = $context->helper->loadValue($stringVar);
$offset = $context->structFieldIndex($argValue, 'length');
$__str__length = $context->builder->load(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,25 @@ public function testEnsureMinimalDropsEagerDomGcProgressAssert(): void
);
}

// Essentials for thin echo / error / argv / getenv surface stay.
// HtmlEntities / Decode / ErrorHandler / ExceptionHandler dropped in #34612 (peer this test).
// LastError dropped in #34631 (peer this test).
// Essentials for thin argv / getenv surface stay (#34695 dropped ObOutput;
// #34641 dropped StringTriggerError).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'StringTriggerError::ensureStandaloneBodies($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34605)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringTriggerError (#34641)'
);
$this->assertStringNotContainsString(
'LastErrorRuntime::ensureStandaloneBodies($this)',
$minimalBody,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,26 @@ public function testEnsureMinimalDropsEagerHtmlErrorHandler(): void
);
}

// Essentials for thin echo / error / argv / getenv surface stay.
// Essentials for thin argv / getenv surface stay (#34695 dropped ObOutput;
// #34641 dropped StringTriggerError).
// LastError dropped in #34631 (peer this test).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'StringTriggerError::ensureStandaloneBodies($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34612)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringTriggerError (#34641)'
);
$this->assertStringNotContainsString(
'LastErrorRuntime::ensureStandaloneBodies($this)',
$minimalBody,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ public function testEnsureMinimalDropsEagerHtmlspecialchars(): void
'ensureMinimalUserStandaloneBodies must not eagerly StringHtmlspecialchars (#34642)'
);

// Essentials for thin echo / error / argv / getenv surface stay.
// Essentials for thin argv / getenv / bridges stay (#34695 dropped ObOutput).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'StringTriggerError::ensureStandaloneBodies($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
Expand All @@ -42,6 +40,16 @@ public function testEnsureMinimalDropsEagerHtmlspecialchars(): void
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34642)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringTriggerError (#34641)'
);
}

public function testCallSiteEnsuresBeforeLookup(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ public function testEnsureMinimalDropsEagerLastError(): void
'ensureMinimalUserStandaloneBodies must not eagerly LastErrorRuntime (#34631)'
);

// Essentials for thin echo / error / argv / getenv surface stay.
// Essentials for thin argv / getenv / bridges stay (#34695 dropped ObOutput).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
Expand All @@ -41,6 +40,11 @@ public function testEnsureMinimalDropsEagerLastError(): void
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34631)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);

$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
Expand Down
102 changes: 102 additions & 0 deletions test/unit/ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPUnit\Framework\TestCase;

/**
* Drop Context::ensureMinimalUserStandaloneBodies always-on ObOutputRuntime (#34695 / peer #34642).
*
* Thin AOT hello-world must not eagerly NestedJIT ob_* ABI; ValueEchoHelper /
* ValueEchoRuntime ensureLinked lazily (#32122 .1 mint class).
*/
final class ContextMinimalStandaloneLazyObOutputRuntimeShrinkTest extends TestCase
{
public function testEnsureMinimalDropsEagerObOutput(): void
{
$context = (string) file_get_contents(__DIR__.'/../../lib/JIT/Context.php');
$this->assertStringContainsString('#34695', $context);
$minimalPos = strpos($context, 'private function ensureMinimalUserStandaloneBodies');
$this->assertNotFalse($minimalPos);
$minimalEnd = strpos($context, 'private function ensureBootstrapAotStandaloneBodies', $minimalPos);
$this->assertNotFalse($minimalEnd);
$minimalBody = substr($context, $minimalPos, $minimalEnd - $minimalPos);

$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimalUserStandaloneBodies must not eagerly ObOutputRuntime (#34695)'
);

// Essentials for thin argv / getenv / bridges stay.
foreach ([
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
'ExceptionBridge::ensureStandaloneBodies($this)',
'ErrorBridge::ensureStandaloneBodies($this)',
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34695)");
}

// ensureFull must not re-add ObOutput before ValueEcho (ValueEcho → ObOutput).
$fullPos = strpos($context, 'private function ensureFullStandaloneBodies');
$this->assertNotFalse($fullPos);
$fullEnd = strpos($context, 'private function ', $fullPos + 1);
$fullHead = false === $fullEnd
? substr($context, $fullPos, 2500)
: substr($context, $fullPos, min(2500, $fullEnd - $fullPos));
$this->assertStringContainsString('ValueEchoRuntime::ensureLinked($this)', $fullHead);
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$fullHead,
'ensureFull must not eagerly ObOutput before ValueEcho (#34695)'
);
}

public function testValueEchoHelperEnsuresBeforeObLookup(): void
{
$source = (string) file_get_contents(__DIR__.'/../../lib/JIT/ValueEchoHelper.php');
$this->assertStringContainsString('#34695', $source);
foreach (['echoLiteral', 'echoNativeLong', 'echoStringVariable'] as $method) {
$pos = strpos($source, 'public static function '.$method);
$this->assertNotFalse($pos, $method);
$next = strpos($source, 'public static function ', $pos + 10);
$body = false === $next
? substr($source, $pos)
: substr($source, $pos, $next - $pos);
$this->assertStringContainsString(
'ObOutputRuntime::ensureLinked($context)',
$body,
$method.' must ensure ObOutput before __phpc_ob_echo_* (#34695)'
);
}
}

public function testJitEchoOpcodeEnsuresBeforeBareObLookup(): void
{
$jit = (string) file_get_contents(__DIR__.'/../../lib/JIT.php');
// Concat/string echo uses bare __phpc_ob_echo_substr in the ECHO opcode switch (#34695).
$this->assertMatchesRegularExpression(
'/case Variable::TYPE_STRING:\s*\/\/ Lazy ob_\* — bare __phpc_ob_echo_\* lookups below \(#34695\)\.\s*JIT\\\\Builtin\\\\ObOutputRuntime::ensureLinked/s',
$jit,
'JIT ECHO TYPE_STRING must ensureLinked before bare __phpc_ob_echo_* (#34695)'
);
$this->assertMatchesRegularExpression(
'/case Variable::TYPE_NATIVE_BOOL:\s*JIT\\\\Builtin\\\\ObOutputRuntime::ensureLinked/s',
$jit,
'JIT ECHO TYPE_NATIVE_BOOL must ensureLinked before bare __phpc_ob_echo_cstr (#34695)'
);
}

public function testNoNewRuntimeCForMinimalObOutputLazy(): void
{
$runtimeDir = dirname(__DIR__, 2).'/lib/AOT/runtime';
$this->assertFileDoesNotExist(
$runtimeDir.'/ob_output.c',
'must not add ob_output.c for #34695 — PHP JIT bridges only'
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ public function testEnsureMinimalDropsEagerJitReturnPending(): void
'ensureMinimalUserStandaloneBodies must not eagerly JitReturnPending (#34621)'
);

// Essentials for thin echo / error / argv / getenv surface stay.
// Essentials for thin argv / getenv / bridges stay (#34695 dropped ObOutput;
// #34641 dropped StringTriggerError).
// LastError dropped in #34631 (peer this test).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'StringTriggerError::ensureStandaloneBodies($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
Expand All @@ -43,6 +42,16 @@ public function testEnsureMinimalDropsEagerJitReturnPending(): void
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34621)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringTriggerError (#34641)'
);
$this->assertStringNotContainsString(
'LastErrorRuntime::ensureStandaloneBodies($this)',
$minimalBody,
Expand Down
15 changes: 12 additions & 3 deletions test/unit/ContextMinimalStandaloneLazyStdlibRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,25 @@ public function testEnsureMinimalDropsEagerStdlibBatch(): void
);
}

// Essentials for thin echo / error / argv / getenv surface stay.
// Essentials for thin argv / getenv surface stay (#34695 dropped ObOutput;
// #34641 dropped StringTriggerError).
foreach ([
'ObOutputRuntime::ensureLinked($this)',
'StringTriggerError::ensureStandaloneBodies($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34578)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringTriggerError::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringTriggerError (#34641)'
);
}

public function testCallSitesEnsureBeforeLookup(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ public function testEnsureMinimalDropsEagerStringTriggerError(): void
'ensureMinimalUserStandaloneBodies must not eagerly StringTriggerError (#34641)'
);

// Essentials for thin echo / argv / getenv / bridges stay.
// Essentials for thin argv / getenv / bridges stay (#34695 dropped ObOutput;
// #34642 dropped StringHtmlspecialchars).
foreach ([
'StringHtmlspecialchars::ensureStandaloneBodies($this)',
'ObOutputRuntime::ensureLinked($this)',
'CliArgvRuntime::ensureStandaloneBodies($this)',
'EnvLocalRuntime::ensureLinked($this)',
'SuperglobalNameRuntime::ensureLinked($this)',
Expand All @@ -42,6 +41,16 @@ public function testEnsureMinimalDropsEagerStringTriggerError(): void
] as $keep) {
$this->assertStringContainsString($keep, $minimalBody, "keep {$keep} in minimal (#34641)");
}
$this->assertStringNotContainsString(
'ObOutputRuntime::ensureLinked($this)',
$minimalBody,
'ensureMinimal must not eagerly ObOutputRuntime (#34695)'
);
$this->assertStringNotContainsString(
'StringHtmlspecialchars::ensureStandaloneBodies($this)',
$minimalBody,
'ensureMinimal must not eagerly StringHtmlspecialchars (#34642)'
);

// Full standalone still links StringTriggerError before AssertFail / LastError.
$fullPos = strpos($context, 'private function ensureFullStandaloneBodies');
Expand Down
Loading
Loading