diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 2a62ecca6cc9..c7169583eaa7 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -27008,7 +27008,7 @@ Rank live CFG gaps across inventory files: `php script/bootstrap-inventory-triag ### `ext/standard/Crc32JitHelper.php` **Warnings** (review for bootstrap subset): -- 2 class method(s) +- 7 class method(s) ### `ext/standard/CslashesJitHelper.php` diff --git a/ext/standard/Crc32JitHelper.php b/ext/standard/Crc32JitHelper.php index ff3d5c63a02d..553c4c29221d 100644 --- a/ext/standard/Crc32JitHelper.php +++ b/ext/standard/Crc32JitHelper.php @@ -5,20 +5,347 @@ namespace PHPCompiler\ext\standard; /** - * crc32()/crc32c() for compiled JIT/AOT modules (#15759, php-in-PHP). + * crc32()/crc32c() for compiled JIT/AOT modules (#15759, #27077, php-in-PHP). * - * SSOT: {@see VmCrc32}, {@see VmCrc32c} + * NestedJIT-safe CRC32B / CRC32C (peer {@see Bin2hexJitHelper} / {@see StrrevJitHelper}). + * Avoid Vm* CRC SSOT call sites and native ord/strlen — NestedJIT stubs those to 0 under thin AOT + * (#16075 / #20452 / #15759 helper-bridge wrong-0). Bit-by-bit poly (no lookup table) — + * NestedJIT AOT array-table updates miscomputed CRC (#27077). * php-src: ext/standard/crc32.c, ext/standard/hash_crc32.c */ final class Crc32JitHelper { + private const UINT32_MASK = 4294967295; + + /** IEEE CRC32B reflected polynomial (crc32.c). */ + private const POLY_CRC32B = 3988292384; // 0xEDB88320 + + /** Castagnoli CRC32C reflected polynomial (hash_crc32.c). */ + private const POLY_CRC32C = 2197175160; // 0x82F63B78 + public static function crc32Argv(string $data, int $seed): int { - return VmCrc32::compute($data, $seed); + $state = self::u32(((int) $seed) ^ self::UINT32_MASK); + $len = self::byteLength($data); + for ($i = 0; $i < $len; ++$i) { + $state = self::update($state, self::byteOrd($data[$i]), self::POLY_CRC32B); + } + + return self::u32(~$state); } public static function crc32cArgv(string $data): int { - return VmCrc32c::compute($data); + $state = self::UINT32_MASK; + $len = self::byteLength($data); + for ($i = 0; $i < $len; ++$i) { + $state = self::update($state, self::byteOrd($data[$i]), self::POLY_CRC32C); + } + + return self::u32(~$state); + } + + private static function update(int $state, int $byte, int $poly): int + { + $state = self::u32($state ^ $byte); + for ($j = 0; $j < 8; ++$j) { + if (($state & 1) !== 0) { + $state = self::u32(($state >> 1) ^ $poly); + } else { + $state = self::u32($state >> 1); + } + } + + return $state; + } + + private static function u32(int $value): int + { + return $value & self::UINT32_MASK; + } + + private static function byteLength(string $data): int + { + $len = 0; + while (isset($data[$len])) { + ++$len; + } + + return $len; + } + + /** NestedJIT-safe byte ordinal (#20452). */ + private static function byteOrd(string $byte): int + { + for ($code = 0; $code < 256; ++$code) { + if ($byte === self::byteAt($code)) { + return $code; + } + } + + return 0; + } + + private static function byteAt(int $code): string + { + return match ($code) { + 0 => "\0", + 1 => "\x01", + 2 => "\x02", + 3 => "\x03", + 4 => "\x04", + 5 => "\x05", + 6 => "\x06", + 7 => "\x07", + 8 => "\x08", + 9 => "\x09", + 10 => "\x0a", + 11 => "\x0b", + 12 => "\x0c", + 13 => "\x0d", + 14 => "\x0e", + 15 => "\x0f", + 16 => "\x10", + 17 => "\x11", + 18 => "\x12", + 19 => "\x13", + 20 => "\x14", + 21 => "\x15", + 22 => "\x16", + 23 => "\x17", + 24 => "\x18", + 25 => "\x19", + 26 => "\x1a", + 27 => "\x1b", + 28 => "\x1c", + 29 => "\x1d", + 30 => "\x1e", + 31 => "\x1f", + 32 => ' ', + 33 => '!', + 34 => '"', + 35 => '#', + 36 => '$', + 37 => '%', + 38 => '&', + 39 => "'", + 40 => '(', + 41 => ')', + 42 => '*', + 43 => '+', + 44 => ',', + 45 => '-', + 46 => '.', + 47 => '/', + 48 => '0', + 49 => '1', + 50 => '2', + 51 => '3', + 52 => '4', + 53 => '5', + 54 => '6', + 55 => '7', + 56 => '8', + 57 => '9', + 58 => ':', + 59 => ';', + 60 => '<', + 61 => '=', + 62 => '>', + 63 => '?', + 64 => '@', + 65 => 'A', + 66 => 'B', + 67 => 'C', + 68 => 'D', + 69 => 'E', + 70 => 'F', + 71 => 'G', + 72 => 'H', + 73 => 'I', + 74 => 'J', + 75 => 'K', + 76 => 'L', + 77 => 'M', + 78 => 'N', + 79 => 'O', + 80 => 'P', + 81 => 'Q', + 82 => 'R', + 83 => 'S', + 84 => 'T', + 85 => 'U', + 86 => 'V', + 87 => 'W', + 88 => 'X', + 89 => 'Y', + 90 => 'Z', + 91 => '[', + 92 => '\\', + 93 => ']', + 94 => '^', + 95 => '_', + 96 => '`', + 97 => 'a', + 98 => 'b', + 99 => 'c', + 100 => 'd', + 101 => 'e', + 102 => 'f', + 103 => 'g', + 104 => 'h', + 105 => 'i', + 106 => 'j', + 107 => 'k', + 108 => 'l', + 109 => 'm', + 110 => 'n', + 111 => 'o', + 112 => 'p', + 113 => 'q', + 114 => 'r', + 115 => 's', + 116 => 't', + 117 => 'u', + 118 => 'v', + 119 => 'w', + 120 => 'x', + 121 => 'y', + 122 => 'z', + 123 => '{', + 124 => '|', + 125 => '}', + 126 => '~', + 127 => "\x7f", + 128 => "\x80", + 129 => "\x81", + 130 => "\x82", + 131 => "\x83", + 132 => "\x84", + 133 => "\x85", + 134 => "\x86", + 135 => "\x87", + 136 => "\x88", + 137 => "\x89", + 138 => "\x8a", + 139 => "\x8b", + 140 => "\x8c", + 141 => "\x8d", + 142 => "\x8e", + 143 => "\x8f", + 144 => "\x90", + 145 => "\x91", + 146 => "\x92", + 147 => "\x93", + 148 => "\x94", + 149 => "\x95", + 150 => "\x96", + 151 => "\x97", + 152 => "\x98", + 153 => "\x99", + 154 => "\x9a", + 155 => "\x9b", + 156 => "\x9c", + 157 => "\x9d", + 158 => "\x9e", + 159 => "\x9f", + 160 => "\xa0", + 161 => "\xa1", + 162 => "\xa2", + 163 => "\xa3", + 164 => "\xa4", + 165 => "\xa5", + 166 => "\xa6", + 167 => "\xa7", + 168 => "\xa8", + 169 => "\xa9", + 170 => "\xaa", + 171 => "\xab", + 172 => "\xac", + 173 => "\xad", + 174 => "\xae", + 175 => "\xaf", + 176 => "\xb0", + 177 => "\xb1", + 178 => "\xb2", + 179 => "\xb3", + 180 => "\xb4", + 181 => "\xb5", + 182 => "\xb6", + 183 => "\xb7", + 184 => "\xb8", + 185 => "\xb9", + 186 => "\xba", + 187 => "\xbb", + 188 => "\xbc", + 189 => "\xbd", + 190 => "\xbe", + 191 => "\xbf", + 192 => "\xc0", + 193 => "\xc1", + 194 => "\xc2", + 195 => "\xc3", + 196 => "\xc4", + 197 => "\xc5", + 198 => "\xc6", + 199 => "\xc7", + 200 => "\xc8", + 201 => "\xc9", + 202 => "\xca", + 203 => "\xcb", + 204 => "\xcc", + 205 => "\xcd", + 206 => "\xce", + 207 => "\xcf", + 208 => "\xd0", + 209 => "\xd1", + 210 => "\xd2", + 211 => "\xd3", + 212 => "\xd4", + 213 => "\xd5", + 214 => "\xd6", + 215 => "\xd7", + 216 => "\xd8", + 217 => "\xd9", + 218 => "\xda", + 219 => "\xdb", + 220 => "\xdc", + 221 => "\xdd", + 222 => "\xde", + 223 => "\xdf", + 224 => "\xe0", + 225 => "\xe1", + 226 => "\xe2", + 227 => "\xe3", + 228 => "\xe4", + 229 => "\xe5", + 230 => "\xe6", + 231 => "\xe7", + 232 => "\xe8", + 233 => "\xe9", + 234 => "\xea", + 235 => "\xeb", + 236 => "\xec", + 237 => "\xed", + 238 => "\xee", + 239 => "\xef", + 240 => "\xf0", + 241 => "\xf1", + 242 => "\xf2", + 243 => "\xf3", + 244 => "\xf4", + 245 => "\xf5", + 246 => "\xf6", + 247 => "\xf7", + 248 => "\xf8", + 249 => "\xf9", + 250 => "\xfa", + 251 => "\xfb", + 252 => "\xfc", + 253 => "\xfd", + 254 => "\xfe", + 255 => "\xff", + default => "\0", + }; } } diff --git a/lib/JIT/Builtin/Crc32Runtime.php b/lib/JIT/Builtin/Crc32Runtime.php index 93449114a758..1122a60537fe 100644 --- a/lib/JIT/Builtin/Crc32Runtime.php +++ b/lib/JIT/Builtin/Crc32Runtime.php @@ -9,10 +9,12 @@ use PHPLLVM\Value; /** - * JIT/AOT link for crc32()/crc32c() via Crc32JitHelper PHP (#15759). + * JIT/AOT link for crc32()/crc32c() via NestedJIT-safe Crc32JitHelper PHP (#15759, #27077). * * Replaces inline CRC table LLVM in ext/standard/JitCrcCore.php. - * SSOT: {@see \PHPCompiler\ext\standard\VmCrc32}, {@see \PHPCompiler\ext\standard\VmCrc32c}. + * Helper compile: {@see JitVmHelperLink::ensureBridge} (MathSqrt #27888 / Bin2hex #20452 shape). + * SSOT parity: {@see \PHPCompiler\ext\standard\VmCrc32}, {@see \PHPCompiler\ext\standard\VmCrc32c} + * (algorithms inlined into the helper — NestedJIT must not call Vm* under thin AOT). */ final class Crc32Runtime { @@ -32,6 +34,10 @@ final class Crc32Runtime self::CRC32C_HELPER, ]; + private const CRC32_BRIDGE_ENTRY = 'crc32_bridge_entry'; + + private const CRC32C_BRIDGE_ENTRY = 'crc32c_bridge_entry'; + public static function ensureLinked(Context $context): void { self::implementCrc32($context); @@ -61,35 +67,49 @@ public static function invokeCrc32c(Context $context, Value $subject): Value private static function implementCrc32(Context $context): void { + $probe = $context->module->getNamedFunction(self::ABI_CRC32); + if (JitVmHelperLink::hasNamedBridgeEntry($probe, self::CRC32_BRIDGE_ENTRY)) { + $context->registerFunction(self::ABI_CRC32, $probe); + + return; + } + $strPtr = $context->getTypeFromString('__string__*'); $i64 = $context->getTypeFromString('int64'); JitVmHelperLink::ensureBridge( $context, self::ABI_CRC32, - 'crc32_bridge_entry', + self::CRC32_BRIDGE_ENTRY, [$strPtr, $i64], $i64, self::CRC32_HELPER, self::HELPER_PATH, self::COMPILED_HELPERS, - '#15759' + '#27077' ); } private static function implementCrc32c(Context $context): void { + $probe = $context->module->getNamedFunction(self::ABI_CRC32C); + if (JitVmHelperLink::hasNamedBridgeEntry($probe, self::CRC32C_BRIDGE_ENTRY)) { + $context->registerFunction(self::ABI_CRC32C, $probe); + + return; + } + $strPtr = $context->getTypeFromString('__string__*'); $i64 = $context->getTypeFromString('int64'); JitVmHelperLink::ensureBridge( $context, self::ABI_CRC32C, - 'crc32c_bridge_entry', + self::CRC32C_BRIDGE_ENTRY, [$strPtr], $i64, self::CRC32C_HELPER, self::HELPER_PATH, self::COMPILED_HELPERS, - '#15759' + '#27077' ); } } diff --git a/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/manifest.json b/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/manifest.json index ef12ba1cff59..51fb2ca9de04 100644 --- a/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/manifest.json +++ b/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/manifest.json @@ -1 +1 @@ -{"fingerprint":"76aa5caaedadabc300e5","fingerprint_version":2,"unit":"/ext/standard/Crc32JitHelper.php","deps":["/ext/standard/Crc32JitHelper.php","/ext/standard/VmCrc32.php","/ext/standard/VmCrc32c.php"],"helpers":{"phpcompiler\\ext\\standard\\crc32jithelper::crc32argv":"PHPCompiler_ext_standard_Crc32JitHelper__crc32argv","phpcompiler\\ext\\standard\\crc32jithelper::crc32cargv":"PHPCompiler_ext_standard_Crc32JitHelper__crc32cargv"},"init_symbol":"__init__unit_ext_standard_Crc32JitHelper_php","shutdown_symbol":"__shutdown__unit_ext_standard_Crc32JitHelper_php","init_via_global_ctor":true,"runtime_safe":true} +{"fingerprint":"13668bf24759d27adf22","fingerprint_version":2,"unit":"/ext/standard/Crc32JitHelper.php","deps":["/ext/standard/Crc32JitHelper.php"],"helpers":{"phpcompiler\\ext\\standard\\crc32jithelper::crc32argv":"PHPCompiler_ext_standard_Crc32JitHelper__crc32argv","phpcompiler\\ext\\standard\\crc32jithelper::crc32cargv":"PHPCompiler_ext_standard_Crc32JitHelper__crc32cargv"},"init_symbol":"__init__unit_ext_standard_Crc32JitHelper_php","shutdown_symbol":"__shutdown__unit_ext_standard_Crc32JitHelper_php","init_via_global_ctor":true,"runtime_safe":true} diff --git a/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/unit.o b/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/unit.o index a1edd490591b..66e2fbecf0b6 100644 Binary files a/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/unit.o and b/prelinked/helper-runtime/x86_64-linux/units/ext_standard_Crc32JitHelper_php/unit.o differ diff --git a/test/aot/AotTest.php b/test/aot/AotTest.php index 55fb6021b4a9..430255d1fd9d 100644 --- a/test/aot/AotTest.php +++ b/test/aot/AotTest.php @@ -247,10 +247,6 @@ public static function providePHPTests(): \Generator if (!CompilerVersion::supportsCrc32c() && str_contains($name, 'crc32c')) { continue; } - // crc32c AOT native execute via helper bridge returns 0 (#15759); VM+JIT compliance covers parity. - if (CompilerVersion::supportsCrc32c() && str_contains($name, 'crc32c')) { - continue; - } // AOT mb_str_pad_*_forward* / json_validate_*_forward* fixtures set PROFILE via --ENV--; always include (#22373, #22544). if (!CompilerVersion::supportsMbStrPad() && str_contains($name, 'mb_str_pad') diff --git a/test/repro/issue_27077_crc32_aot.php b/test/repro/issue_27077_crc32_aot.php new file mode 100644 index 000000000000..ba23efab603a --- /dev/null +++ b/test/repro/issue_27077_crc32_aot.php @@ -0,0 +1,3 @@ +assertFileDoesNotExist(__DIR__.'/../../ext/standard/JitCrcCore.php'); } + public function testCrc32JitHelperIsNestedJitSelfContained(): void + { + $source = (string) file_get_contents(__DIR__.'/../../ext/standard/Crc32JitHelper.php'); + // Call sites only — docblock may name the VM SSOT / banned builtins for humans. + $this->assertStringNotContainsString('VmCrc32::', $source); + $this->assertStringNotContainsString('VmCrc32c::', $source); + $this->assertStringNotContainsString('VmString::', $source); + $this->assertStringNotContainsString('\\ord(', $source); + $this->assertStringNotContainsString('\\strlen(', $source); + } + public function testCrc32JitHelperMatchesVmSsot(): void { $this->assertSame(VmCrc32::compute('abc', 0), Crc32JitHelper::crc32Argv('abc', 0)); $this->assertSame(VmCrc32c::compute('abc'), Crc32JitHelper::crc32cArgv('abc')); + $this->assertSame(4282364586, Crc32JitHelper::crc32Argv('php-compiler', 0)); + $seed = VmCrc32::compute('f', 0); + $this->assertSame(VmCrc32::compute('oo', $seed), Crc32JitHelper::crc32Argv('oo', $seed)); } } diff --git a/test/unit/Crc32cBuiltinTest.php b/test/unit/Crc32cBuiltinTest.php index 303ee1d8a178..f65ea43ea5c4 100644 --- a/test/unit/Crc32cBuiltinTest.php +++ b/test/unit/Crc32cBuiltinTest.php @@ -39,7 +39,6 @@ public function testVmMatchesPhpSubset(): void */ public function testAotNativeBinaryMatchesPhpSubset(): void { - $this->markTestSkipped('crc32/crc32c AOT native execute via helper bridge returns 0 (#15759); VM+JIT compliance green'); if (!LlvmToolchain::isReady(dirname(__DIR__, 2))) { $this->markTestSkipped('LLVM 9 toolchain not available'); }