Skip to content

Runtime: fixed static arenas give hello-world a 70 MB .bss and the cycle collector silently stops tracking objects after 65,536 (lib/JIT/Builtin/GcCollectCyclesRuntime.php, lib/JIT/Builtin/StreamGlobalsJit.php) #36195

Description

@PurHur

Category

Foundation: · runtime memory model · child of #36188

Problem

size -A build/audit/hello on master 4eed6a2785: .bss 69,876,800 B (23,765 BSS symbols) against .text 12.6 MB. The big ones:

symbol bytes source
phpc_stream_write_buffer_storage 2,097,152 StreamGlobalsJit::GLOBAL_WRITE_BUFFER_STORAGE
__phpc_ob_storage 524,288 output buffering
phpc_gc_objects 524,288 GcCollectCyclesRuntime::MAX_OBJECTS = 65536 × 8
phpc_gc_prop_counts, phpc_gc_inbound 262,144 each same registry
phpc_gc_marked, phpc_destruct_invoked 65,536 each same

BSS is not file size, but it is committed address space and page faults on first touch, and — the real defect — the sizes are hard capacity limits:

phpc_gc_register (GcCollectCyclesRuntime.php ~L335-375):

$atMax = icmp sge count, MAX_OBJECTS
$skip  = or objNull, atMax
branchIf $skip -> done          ; object is silently never registered

After 65,536 live objects have been registered, every further object is invisible to gc_collect_cycles(); cycles among them leak for the life of the process with no diagnostic. A long-running phpc fcgi worker or any script building >64k objects hits this. Zend's GC buffer grows (gc_grow_root_buffer).

php-src reference

  • Zend/zend_gc.cGC_DEFAULT_BUF_SIZE 16K roots, gc_grow_root_buffer() doubles up to GC_MAX_BUF_SIZE; the buffer is heap-allocated on first use.

PHP implementation target

  • GcCollectCyclesRuntime: replace the five fixed [MAX_OBJECTS x T] globals with a heap-allocated, growable registry (phpc_gc_buffer_size global already exists — use it): realloc on full, cap only at an explicit PHP_COMPILER_GC_MAX_ROOTS with a stderr warning when reached.
  • StreamGlobalsJit / OB storage: allocate lazily on first write (malloc in the first write), keep sizes as initial capacity, grow geometrically.
  • Add gc_status() parity (runs, collected, roots, buffer_size) so the change is observable.

Repro

./script/docker-exec.sh -- bash -lc 'source script/php-env.sh && cat > build/gc.php <<'"'"'EOP'"'"'
<?php class N { public $o; } for ($r = 0; $r < 3; $r++) { for ($i = 0; $i < 100000; $i++) { $a = new N; $b = new N; $a->o = $b; $b->o = $a; } echo gc_collect_cycles(), "\n"; }
EOP
php bin/compile.php -o build/gc build/gc.php && ./build/gc && php build/gc.php && size -A build/gc | grep bss'

Zend collects ~200,000 per round; today AOT collects at most 65,536 and .bss is 70 MB.

Done when

  • The probe collects the same counts as Zend in every round (within gc_collect_cycles semantics); a test/differential/cases/ case pins it
  • .bss of hello-world < 4 MB
  • No fixed MAX_OBJECTS array remains; phpc fcgi soak (script/fastcgi-smoke.sh with 200k requests) shows flat RSS

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-3:aotPhase 3 – AOT deployment

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions