You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Runtime: every string copy is an eager memcpy — __string__separate ignores the refcount, no copy-on-write (lib/JIT/Builtin/Type/String_.php, lib/JIT/JitValueBox.php) #36192
Foundation: · runtime value model · child of #36188
Problem
%__string__ = <{ %__ref__ (refcount+flags), i64 len, i8 data… }> carries a refcount, but assignment does not use it. JitValueBox::copyBetweenPointers() (lib/JIT/JitValueBox.php:638-) lowers the string arm of every value copy as
and String_::implementSeparate() (lib/JIT/Builtin/Type/String_.php:748-790) is __string__alloc(len) + memcpy with no refcount check. So $b = $a;, passing a string argument, returning a string, storing into an array element, and every boxed temporary copies the bytes. A 1 MB string passed through three functions is copied three times; string-heavy code (templating, JSON, HTML building) pays O(len) per assignment where Zend pays an increment.
The generated code for this arm is also emitted inline at each of the 176 copy* call sites (see the codegen-bloat sibling issue).
php-src reference
Zend/zend_string.h — zend_string_copy is GC_ADDREF; zend_string_separate/SEPARATE_STRING only copies when GC_REFCOUNT > 1 and a write is about to happen; interned strings (IS_STR_INTERNED) are never refcounted or copied.
PHP implementation target
JitValueBox::copyBetweenPointers string arm → __ref__addref only (immortal/interned strings skip even that; the __ref__ flags already carry an interned bit — __init__ clears bit 0 on constants).
Every in-place mutation site (.=, $s[$i] = 'x', str_* helpers that write into their argument, __string__realloc users) must call a __string__separate_for_write that copies only if refcount > 1.
__value__valueDelref for strings must free at refcount 0 (verify __ref__delref already does).
Category
Foundation:· runtime value model · child of #36188Problem
%__string__ = <{ %__ref__ (refcount+flags), i64 len, i8 data… }>carries a refcount, but assignment does not use it.JitValueBox::copyBetweenPointers()(lib/JIT/JitValueBox.php:638-) lowers the string arm of every value copy asand
String_::implementSeparate()(lib/JIT/Builtin/Type/String_.php:748-790) is__string__alloc(len)+memcpywith no refcount check. So$b = $a;, passing a string argument, returning a string, storing into an array element, and every boxed temporary copies the bytes. A 1 MB string passed through three functions is copied three times; string-heavy code (templating, JSON, HTML building) pays O(len) per assignment where Zend pays an increment.The generated code for this arm is also emitted inline at each of the 176
copy*call sites (see the codegen-bloat sibling issue).php-src reference
zend_string_copyisGC_ADDREF;zend_string_separate/SEPARATE_STRINGonly copies whenGC_REFCOUNT > 1and a write is about to happen; interned strings (IS_STR_INTERNED) are never refcounted or copied.PHP implementation target
JitValueBox::copyBetweenPointersstring arm →__ref__addrefonly (immortal/interned strings skip even that; the__ref__flags already carry an interned bit —__init__clears bit 0 on constants)..=,$s[$i] = 'x',str_*helpers that write into their argument,__string__reallocusers) must call a__string__separate_for_writethat copies only if refcount > 1.__value__valueDelreffor strings must free at refcount 0 (verify__ref__delrefalready does).--repeat 10and undervalgrind/ASan for one sweep: this changes ownership rules, and the repo's characteristic bug is intermittent heap corruption (AOT: script-scope ++/-- goes wrong once several statements or variables are involved (loop after statements, second variable) #23842).Repro
Today AOT copies 20 GB; Zend does 20k increments.
Done when
test/differential/cases/batch covering aliasing semantics: copy-then-mutate original, mutate copy,.=on a shared string, by-ref string params, string in array copied then modified,str_repeat/substrresultsscript/differential-sweep.sh --aot --repeat 10green; no new leaks (PHP_COMPILER_VM_RSS_GUARD-style peak RSS check on the probe)