Skip to content

Codegen: every boxed assignment open-codes a 15-block type switch with duplicated error blocks — 111k-line IR module and 51 KB helper functions for a 4-line script (lib/JIT/JitValueBox.php) #36193

Description

@PurHur

Category

Foundation: · generated-code size / compile time · child of #36188

Problem

JitValueBox::copyBetweenPointers() (lib/JIT/JitValueBox.php:623-800) appends value_copy_{string,hashtable,object,long,double,bool,null,done} plus seven after_* blocks — 15 basic blocks, tagged by a static counter so nothing is shared — at every boxed assignment site (176 copy* call sites in lib/). The same pattern repeats for error paths: each op gets its own __compiler_jit_raise_type_error + abort block.

Measured on the committed IR dump build/micro/m_loop.ll (4-line typed loop, 111,127 IR lines, 485 defined functions):

function IR lines of which
PHPCompiler_ext_standard_IniJitHelper__inicfgget 16,508 1,176 __value__valueDelref, 759 __compiler_jit_raise_type_error + abort pairs, 167 __string__separate
…IniJitHelper__iniget 12,187 (51 KB of .text in the linked binary)
…PendingHeadersJitHelper__statuscodefromhttpversionline 51 KB of .text for a status-line parser
user function loop 40

The user program is 0.04 % of the module. Everything else is helper corpus re-lowered per unit, and its size is set by this open-coding. It costs LLVM verify/codegen time on every build, unit.o size (see the helper-unit duplication issue), and makes any optimisation pass 25x more expensive than the program warrants.

php-src reference

  • Zend/zend_variables.hZVAL_COPY / ZVAL_COPY_VALUE are one inline function each; the type switch lives in zval_add_ref, not at every call site.

PHP implementation target

  • Emit the body of copyBetweenPointers once per module as __value__copy(%__value__* dst, %__value__* src) (and __value__assign = delref old + copy) with alwaysinline so that the minimal pass pipeline (sibling issue) can still inline where profitable; call sites become one call.
  • Outline the cold error paths: one __jit_type_error_abort(msg, code) noreturn function per module; call sites br to a single shared cold block per function.
  • Same treatment for JitValueNumeric::emitBoxedNumericResult (lib/JIT/JitValueNumeric.php:373-448): load each operand's tag once; valueIsDouble/valueIsString/valueBoxToDouble re-load it 4x today.
  • Measure with PHP_COMPILER_DUMP_IR=1 (writes /tmp/phpc-last.ll): IR lines, define count, and .text of the hello-world binary before/after.

Repro

./script/docker-exec.sh -- bash -lc 'source script/php-env.sh && printf "<?php echo 1;\n" > build/one.php \
  && PHP_COMPILER_DUMP_IR=1 php bin/compile.php -o build/one build/one.php && wc -l /tmp/phpc-last.ll \
  && grep -c "value_copy_string_" /tmp/phpc-last.ll && size build/one'

Done when

  • IR line count of the echo 1 module drops ≥ 40 %; value_copy_string_ labels ≤ 1 per module
  • Hello-world .text (size) drops accordingly; committed helper-unit cache refreshed once (make helper-runtime-prelink-refresh) and its total size recorded in the PR
  • script/aot-smoke.sh 8/8; script/differential-sweep.sh --aot --repeat 3 failing-name set unchanged; PHP_COMPILER_OPT_LEVEL=2 build of Ack(3,9) still 4093

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

    IMPORTANTCritical patharea:compilerCompiler / CFG / JITenhancementNew feature or requestimplementation-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