diff --git a/lib/JIT/Builtin/Refcount.php b/lib/JIT/Builtin/Refcount.php index ab2b29de422..437ac7b12cd 100644 --- a/lib/JIT/Builtin/Refcount.php +++ b/lib/JIT/Builtin/Refcount.php @@ -172,15 +172,28 @@ public function register(): void { } public function implement(): void { - \PHPCompiler\JIT\Builtin\WeakRefRuntime::ensureLinked($this->context); - \PHPCompiler\JIT\Builtin\WeakRefNative::registerDeclarations($this->context); - \PHPCompiler\JIT\Builtin\GcCollectCyclesRuntime::ensureDeclarations($this->context); + // WeakRef / GC delref always-on ensureLinked removed (#35802): only + // implementDelref lookupFunction phpc_weakref_clear_object_typed / + // phpc_gc_unregister / phpc_destruct_* — call ensureDelrefHelperAbis there + // (peer #35751 HashTable lazy strtol, #35626 strcoll). Thin hello-world must + // not NestedJIT weakref/gc ABIs during Refcount init — leftover NestedJIT vs + // Runtime ABI drift mints phpc_weakref_*.1 / phpc_gc_*.1 (#31894 / #32122). $this->implementInit(); $this->implementAddref(); $this->implementDelref(); $this->implementSeparate(); } + /** + * WeakReference clear + GC unregister + deferred-destruct probes — delref only (#35802). + */ + private function ensureDelrefHelperAbis(): void + { + \PHPCompiler\JIT\Builtin\WeakRefRuntime::ensureLinked($this->context); + \PHPCompiler\JIT\Builtin\WeakRefNative::registerDeclarations($this->context); + \PHPCompiler\JIT\Builtin\GcCollectCyclesRuntime::ensureDeclarations($this->context); + } + private function implementInit(): void { // insertValue returns a new aggregate — must store it (#24226: prior codegen // discarded the SSA value, so refcount/typeinfo never hit memory). @@ -254,6 +267,7 @@ private function implementAddref(): void { } private function implementDelref(): void { + $this->ensureDelrefHelperAbis(); $fn___8f14e45fceea167a5a36dedd4bea2543 = $this->context->lookupFunction('__ref__delref'); $block___8f14e45fceea167a5a36dedd4bea2543 = $fn___8f14e45fceea167a5a36dedd4bea2543->appendBasicBlock('main'); $this->context->builder->positionAtEnd($block___8f14e45fceea167a5a36dedd4bea2543); diff --git a/lib/JIT/Builtin/Refcount.pre b/lib/JIT/Builtin/Refcount.pre index c335503ee4f..2c4e853b6d7 100755 --- a/lib/JIT/Builtin/Refcount.pre +++ b/lib/JIT/Builtin/Refcount.pre @@ -57,14 +57,23 @@ class Refcount extends Builtin { } public function implement(): void { - \PHPCompiler\JIT\Builtin\WeakRefRuntime::ensureLinked($this->context); - \PHPCompiler\JIT\Builtin\WeakRefNative::registerDeclarations($this->context); + // WeakRef / GC delref always-on ensureLinked removed (#35802): only + // implementDelref emits phpc_weakref_clear_object_typed / phpc_gc_unregister / + // phpc_destruct_* — call ensureDelrefHelperAbis there (peer #35751). $this->implementInit(); $this->implementAddref(); $this->implementDelref(); $this->implementSeparate(); } + /** WeakReference clear + GC unregister + deferred-destruct probes — delref only (#35802). */ + private function ensureDelrefHelperAbis(): void + { + \PHPCompiler\JIT\Builtin\WeakRefRuntime::ensureLinked($this->context); + \PHPCompiler\JIT\Builtin\WeakRefNative::registerDeclarations($this->context); + \PHPCompiler\JIT\Builtin\GcCollectCyclesRuntime::ensureDeclarations($this->context); + } + private function implementInit(): void { // NOTE: compile{} expands `$ref.refcount = …` to insertValue without store. // The generated Refcount.php hand-stores the aggregate (#24226). Re-run the @@ -101,6 +110,7 @@ class Refcount extends Builtin { } private function implementDelref(): void { + $this->ensureDelrefHelperAbis(); compile { function __ref__delref($refVirtual) { $isNull = $refVirtual == null; diff --git a/test/unit/RefcountLazyWeakRefRuntimeShrinkTest.php b/test/unit/RefcountLazyWeakRefRuntimeShrinkTest.php new file mode 100644 index 00000000000..cc327e840d8 --- /dev/null +++ b/test/unit/RefcountLazyWeakRefRuntimeShrinkTest.php @@ -0,0 +1,87 @@ +assertStringContainsString('#35802', $source); + $pos = strpos($source, 'public function implement(): void'); + $this->assertNotFalse($pos); + $next = strpos($source, 'private function ensureDelrefHelperAbis', $pos); + $this->assertNotFalse($next); + $body = substr($source, $pos, $next - $pos); + foreach ([ + 'WeakRefRuntime::ensureLinked', + 'WeakRefNative::registerDeclarations', + 'GcCollectCyclesRuntime::ensureDeclarations', + ] as $forbidden) { + $this->assertStringNotContainsString( + $forbidden, + $body, + 'Refcount::implement must not eagerly '.$forbidden.' (#35802)' + ); + } + } + + public function testImplementDelrefEnsuresWeakRefGcBeforeLookup(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Refcount.php'); + $fnPos = strpos($source, 'private function implementDelref(): void'); + $this->assertNotFalse($fnPos); + $nextFn = strpos($source, 'private function implementSeparate(): void', $fnPos); + $this->assertNotFalse($nextFn); + $chunk = substr($source, $fnPos, $nextFn - $fnPos); + $this->assertStringContainsString( + 'ensureDelrefHelperAbis', + $chunk, + 'implementDelref must ensureDelrefHelperAbis before lookup (#35802)' + ); + $ensurePos = strpos($chunk, 'ensureDelrefHelperAbis'); + $lookupPos = strpos($chunk, "lookupFunction('phpc_weakref_clear_object_typed')"); + $this->assertNotFalse($ensurePos); + $this->assertNotFalse($lookupPos); + $this->assertLessThan($lookupPos, $ensurePos); + } + + public function testRefcountPreMatchesLazyDelrefPattern(): void + { + $source = (string) file_get_contents(__DIR__.'/../../lib/JIT/Builtin/Refcount.pre'); + $this->assertStringContainsString('#35802', $source); + $this->assertStringContainsString('ensureDelrefHelperAbis', $source); + $pos = strpos($source, 'public function implement(): void'); + $this->assertNotFalse($pos); + $next = strpos($source, 'private function ensureDelrefHelperAbis', $pos); + $this->assertNotFalse($next); + $body = substr($source, $pos, $next - $pos); + $this->assertStringNotContainsString('WeakRefRuntime::ensureLinked', $body); + } + + public function testNoNewRuntimeCForLazyWeakRefAbis(): void + { + $runtimeDir = dirname(__DIR__, 2).'/lib/AOT/runtime'; + foreach ([ + 'weakref.c', + 'phpc_weakref.c', + 'gc_unregister.c', + ] as $name) { + $this->assertFileDoesNotExist( + $runtimeDir.'/'.$name, + 'must not add '.$name.' for #35802 — PHP JIT bridges only' + ); + } + } +}