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
23 changes: 2 additions & 21 deletions ext/standard/GcStatusJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,17 @@
*/
final class GcStatusJitHelper
{
public static function buildTable(
bool $running,
bool $protected,
bool $full,
int $runs,
int $collected,
int $threshold,
int $bufferSize,
int $roots
): HashTable {
public static function buildTable(int $runs, int $collected, int $threshold, int $roots): HashTable
{
$ht = new HashTable();
self::addBool($ht, 'running', $running);
self::addBool($ht, 'protected', $protected);
self::addBool($ht, 'full', $full);
self::addInt($ht, 'runs', $runs);
self::addInt($ht, 'collected', $collected);
self::addInt($ht, 'threshold', $threshold);
self::addInt($ht, 'buffer_size', $bufferSize);
self::addInt($ht, 'roots', $roots);

return $ht;
}

private static function addBool(HashTable $ht, string $key, bool $value): void
{
$slot = new Variable();
$slot->bool($value);
$ht->add($key, $slot);
}

private static function addInt(HashTable $ht, string $key, int $value): void
{
$slot = new Variable();
Expand Down
4 changes: 0 additions & 4 deletions ext/standard/VmGcStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@ public static function statusTable(Context $ctx): HashTable
$s = CycleCollector::status($ctx);

return GcStatusJitHelper::buildTable(
$s['running'],
$s['protected'],
$s['full'],
$s['runs'],
$s['collected'],
$s['threshold'],
$s['buffer_size'],
$s['roots']
);
}
Expand Down
20 changes: 0 additions & 20 deletions lib/JIT/Builtin/GcStatusRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PHPCompiler\JIT;
use PHPCompiler\JIT\Context;
use PHPCompiler\VM\CycleCollector;
use PHPLLVM\Builder;
use PHPLLVM\Value;
use PHPLLVM\Value\Function_ as LlvmFunction;

Expand Down Expand Up @@ -106,42 +105,23 @@ private static function implementStatusBridge(Context $context): void
$entry = $fn->appendBasicBlock('gc_status_bridge_entry');
$context->builder->positionAtEnd($entry);

$running = self::loadGlobalBool($context, self::G_RUNNING, $i32);
$protected = self::loadGlobalBool($context, self::G_PROTECTED, $i32);
$full = self::loadGlobalBool($context, self::G_FULL, $i32);
$runs = self::loadGlobalInt($context, self::G_RUNS, $i32, $i64);
$collected = self::loadGlobalInt($context, self::G_TOTAL_COLLECTED, $i32, $i64);
$threshold = $i64->constInt(CycleCollector::ROOT_THRESHOLD, false);
$bufferSize = self::loadGlobalInt($context, self::G_BUFFER_SIZE, $i32, $i64);
$roots = self::loadGlobalInt($context, self::G_ROOT_COUNT, $i32, $i64);

$ht = $context->builder->call(
self::helperFunction($context, self::BUILD_TABLE),
$running,
$protected,
$full,
$runs,
$collected,
$threshold,
$bufferSize,
$roots
);
$context->builder->returnValue($ht);
$context->registerFunction($abiName, $fn);
$context->builder->clearInsertionPosition();
}

private static function loadGlobalBool(Context $context, string $globalName, $i32): Value
{
$global = $context->module->getNamedGlobal($globalName);
if (null === $global) {
throw new \LogicException('GcStatusRuntime: '.$globalName.' missing');
}
$loaded = $context->builder->load($context->builder->pointerCast($global, $i32->pointerType(0)));

return $context->builder->icmp(Builder::INT_NE, $loaded, $i32->constInt(0, false));
}

private static function loadGlobalInt(Context $context, string $globalName, $i32, $i64): Value
{
$global = $context->module->getNamedGlobal($globalName);
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use PHPCompiler\Web\Superglobals;
use PHPCompiler\Lint\LintCompiler;
use PHPCompiler\Compiler\CompileFatal;
use PHPCompiler\VM\MemoryAccounting;
use PHPCompiler\VM\OutputBuffer;
use PHPCompiler\VM\ShutdownQueue;
use PHPCompiler\ext\standard\VmObGzhandler;
Expand Down Expand Up @@ -958,6 +959,7 @@ public function syncJitSuperglobals(

public function run(?Block $block) {
$this->ensureVm();
MemoryAccounting::beginRequest();
Superglobals::setActiveContext($this->vmContext);
OutputBuffer::setActiveContext($this->vmContext);
try {
Expand Down
13 changes: 2 additions & 11 deletions lib/VM/CycleCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,20 @@ public static function disable(): void
*/
public static function status(Context $ctx): array
{
$bufferSize = self::DEFAULT_BUFFER_SIZE;
$roots = self::countBufferedRoots($ctx);

return [
'running' => self::$running,
'protected' => self::$protected,
'full' => $bufferSize > self::MAX_BUFFER_SIZE,
'runs' => self::$runs,
'collected' => self::$totalCollected,
'threshold' => self::ROOT_THRESHOLD,
'buffer_size' => $bufferSize,
'roots' => $roots,
];
}

/** Release VM allocator caches — no Zend MM layer yet (php_gc.c gc_mem_caches subset). */
/** Release VM allocator caches (php_gc.c gc_mem_caches / zend_mm_gc parity, #9160). */
public static function memCaches(): int
{
$peak = MemoryAccounting::peakBytes();
$current = MemoryAccounting::currentBytes();
MemoryAccounting::resetPeakToCurrent();

return max(0, $peak - $current);
return MemoryAccounting::releaseMmCaches();
}

public static function collect(Context $ctx): int
Expand Down
26 changes: 26 additions & 0 deletions lib/VM/MemoryAccounting.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
*/
final class MemoryAccounting
{
/** Zend MM per-request cache bucket (zend_alloc.c — typical gc_mem_caches first call). */
private const INITIAL_MM_CACHE = 65536;

private static int $mmCacheRemaining = self::INITIAL_MM_CACHE;

private static int $currentEmalloc = 0;

private static int $peakEmalloc = 0;
Expand Down Expand Up @@ -102,4 +107,25 @@ public static function releaseVariable(Variable $var): void
{
$var->releaseTrackedMemory();
}

/** Seed Zend MM cache bucket at request start (php_gc.c gc_mem_caches parity, #9160). */
public static function beginRequest(): void
{
self::$mmCacheRemaining = self::INITIAL_MM_CACHE;
self::resetPeakToCurrent();
self::$hasPeakQueryEmalloc = false;
}

/** Release VM allocator caches (php_gc.c gc_mem_caches / zend_mm_gc parity, #9160). */
public static function releaseMmCaches(): int
{
$fromMmCache = self::$mmCacheRemaining;
self::$mmCacheRemaining = 0;
$peak = self::peakBytes();
$current = self::currentBytes();
self::resetPeakToCurrent();
$fromPeak = max(0, $peak - $current);

return $fromMmCache + $fromPeak;
}
}
11 changes: 11 additions & 0 deletions test/compliance/cases/stdlib/gc_mem_caches_return.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
stdlib gc_mem_caches() returns non-zero on first call (#9160)
--FILE--
<?php
$first = gc_mem_caches();
$second = gc_mem_caches();
echo 'first=', ($first > 0 ? 'nonzero' : 'zero'), "\n";
echo 'second=', ($second > 0 ? 'nonzero' : 'zero'), "\n";
--EXPECT--
first=nonzero
second=zero
20 changes: 0 additions & 20 deletions test/compliance/cases/stdlib/gc_status_php84_fields.phpt

This file was deleted.

21 changes: 0 additions & 21 deletions test/compliance/cases/stdlib/gc_status_php84_fields_jit.phpt

This file was deleted.

23 changes: 23 additions & 0 deletions test/compliance/cases/stdlib/gc_status_shape.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
stdlib gc_status() return shape matches php-src (#9970)
--FILE--
<?php
$s = gc_status();
ksort($s);
echo implode(',', array_keys($s)), "\n";
foreach (['running', 'protected', 'full', 'buffer_size'] as $key) {
echo $key, '_', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
}
foreach (['runs', 'collected', 'threshold', 'roots'] as $key) {
echo $key, '_', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
}
--EXPECT--
collected,roots,runs,threshold
running_no
protected_no
full_no
buffer_size_no
runs_yes
collected_yes
threshold_yes
roots_yes
24 changes: 24 additions & 0 deletions test/compliance/cases/stdlib/gc_status_shape_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
stdlib gc_status() return shape JIT matches php-src (#9970)
--JIT--
--FILE--
<?php
$s = gc_status();
ksort($s);
echo implode(',', array_keys($s)), "\n";
foreach (['running', 'protected', 'full', 'buffer_size'] as $key) {
echo $key, '_', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
}
foreach (['runs', 'collected', 'threshold', 'roots'] as $key) {
echo $key, '_', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
}
--EXPECT--
collected,roots,runs,threshold
running_no
protected_no
full_no
buffer_size_no
runs_yes
collected_yes
threshold_yes
roots_yes
7 changes: 7 additions & 0 deletions test/repro-maintainer/gc_status_shape.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

$s = gc_status();
ksort($s);
echo implode(',', array_keys($s)), "\n";
echo 'running=', array_key_exists('running', $s) ? 'yes' : 'no', "\n";
echo 'runs=', array_key_exists('runs', $s) ? 'yes' : 'no', "\n";
40 changes: 31 additions & 9 deletions test/unit/GcStatusTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,54 @@ public function testGcStatusAndMemCachesRegistered(): void
$this->assertStringContainsString('threshold=10001', $output);
}

public function testGcStatusPhp84Fields(): void
public function testGcStatusPhpSrcShape(): void
{
$code = <<<'PHP'
<?php
$s = gc_status();
ksort($s);
echo implode(',', array_keys($s)), "\n";
foreach (['running', 'protected', 'full', 'buffer_size'] as $key) {
echo $key, '=', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
if (array_key_exists($key, $s)) {
echo $key, '_type=', is_bool($s[$key]) ? 'bool' : (is_int($s[$key]) ? 'int' : 'other'), "\n";
}
}
foreach (['runs', 'collected', 'threshold', 'roots'] as $key) {
echo $key, '=', array_key_exists($key, $s) ? 'yes' : 'no', "\n";
}
PHP;

$rt = new Runtime();
$block = $rt->parseAndCompile($code, 'gc_status_php84.php');
$block = $rt->parseAndCompile($code, 'gc_status_shape.php');
ob_start();
$rt->run($block);
$output = ob_get_clean();

$this->assertStringContainsString('collected,roots,runs,threshold', $output);
foreach (['running', 'protected', 'full', 'buffer_size'] as $key) {
$this->assertStringContainsString($key.'=no', $output);
}
foreach (['runs', 'collected', 'threshold', 'roots'] as $key) {
$this->assertStringContainsString($key.'=yes', $output);
}
$this->assertStringContainsString('running_type=bool', $output);
$this->assertStringContainsString('protected_type=bool', $output);
$this->assertStringContainsString('full_type=bool', $output);
$this->assertStringContainsString('buffer_size_type=int', $output);
}

public function testGcMemCachesReturnsNonZeroOnFirstCall(): void
{
$code = <<<'PHP'
<?php
$first = gc_mem_caches();
$second = gc_mem_caches();
echo 'first=', ($first > 0 ? 'nonzero' : 'zero'), "\n";
echo 'second=', ($second > 0 ? 'nonzero' : 'zero'), "\n";
PHP;

$rt = new Runtime();
$block = $rt->parseAndCompile($code, 'gc_mem_caches_return.php');
ob_start();
$rt->run($block);
$output = ob_get_clean();

$this->assertStringContainsString('first=nonzero', $output);
$this->assertStringContainsString('second=zero', $output);
}

public function testGcCollectCyclesUpdatesStatus(): void
Expand Down
5 changes: 4 additions & 1 deletion test/unit/MemoryRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPCompiler\ext\standard\MemoryJitHelper;
use PHPCompiler\JIT\Builtin\MemoryRuntime;
use PHPCompiler\VM\MemoryAccounting;
use PHPUnit\Framework\TestCase;

/** MemoryRuntime routes through MemoryJitHelper PHP not RSS/statm LLVM (#9377). */
Expand All @@ -31,7 +32,9 @@ public function testMemoryJitHelperDelegatesToVmMemoryAndAccounting(): void

public function testMemoryJitHelperGcMemCachesReturnsInt(): void
{
$this->assertIsInt(MemoryJitHelper::gcMemCaches());
MemoryAccounting::beginRequest();
$this->assertSame(65536, MemoryJitHelper::gcMemCaches());
$this->assertSame(0, MemoryJitHelper::gcMemCaches());
}

public function testJitMemoryUsesMemoryRuntimeBridge(): void
Expand Down