import { createHash } from "node:crypto"; type InlineCacheEntry = readonly [opcode: number, codeUnits: number]; const inlineCacheProfiles = ( magics: readonly number[], entries: readonly InlineCacheEntry[], ): ReadonlyArray]> => { const profile = new Map(entries); return magics.map((magic) => [magic, profile]); }; /** * CPython equality skips cache code units after comparing original code size. * These tables are pinned to CPython bytecode snapshots, not the shared marshal * layout. The 3.11 profile is from v3.11.9; commit * 616622cab7200bac476bf753d6cc98f4ee48808c adds magic 3495 with the same * opcode/cache table. 3.12 magic/profile transitions * follow CPython's v3.12.12 magic history and these cache-changing source * commits: f012df706cf3a9e63625b8166fe3f419855e9a94 (3503), * b083450f8896bb4a29ac522e4474d91c056b9f32 (3504), * 5fcfdd87c9b5066a581d3ccb4b2fede938f343ec (3505), * 6e4e14d98fe0868981f29701496d57a8223c5407 (3515), * 7b14c2ef194b6eed79670aa9d7e29ab8e2256a56 (3516), * 160f2fe2b90ed5ec7838cb4141dd35768422891f (3519), * 08b67fb34f4519be1b0bb4673643a2c761c7ae92 (3521), * 0444ae24875489dc290d8efdb9ee6440ee60dac8 (3523), * 121057aa3600c4d4d392539aeb79e1b09fd5659d (3524), * b4978ff872be5102117b4e25d93dbbb4e04c8292 (3525), * ef25febcf2ede92a03c5ea00a13e167e0b5cb274 (3528), and * 77262458fe3fe9f3f0266bbf578675a906b1e353 (3530). The 3.13 magic history * is recorded in CPython's Lib/importlib/_bootstrap_external.py; opcode/cache sources include * 1d857da7f0e4858e561223f319ae5afe737d5657 (3553/3554), * 665a4391e10167dad1c854fb604c86f336fcd331 (3559), * 3b2ecbc1275bd05534885cee9ac1389987238561 (3560), * bcce5e271815c0bdbe894964e853210d2c75949b (3561), * 8b55adfa8ff05477b4be7def36db7b66c73f181d (3562), * 22e65eecaad3f5337862319687047afe9861e1ef (3563), and * 52cc4af6ae9002f11605f91b672746c127494efd (3564). Adjacent magics share a * profile only where their source changes do not alter cache widths or IDs. */ const INLINE_CACHE_ENTRIES_BY_MAGIC: ReadonlyMap< number, ReadonlyMap > = new Map([ ...inlineCacheProfiles( [3494, 3495], [ [25, 4], [60, 1], [92, 1], [95, 4], [106, 4], [107, 2], [116, 5], [122, 1], [160, 10], [166, 1], [171, 4], ], ), ...inlineCacheProfiles( [3500, 3501, 3502], [ [25, 4], [60, 1], [92, 1], [95, 4], [106, 4], [107, 2], [116, 5], [122, 1], [160, 10], [171, 4], ], ), ...inlineCacheProfiles( [3503], [ [25, 4], [60, 1], [92, 1], [95, 4], [106, 4], [107, 2], [116, 5], [122, 1], [160, 9], [171, 4], ], ), ...inlineCacheProfiles( [3504], [ [25, 4], [60, 1], [92, 1], [95, 4], [106, 9], [107, 2], [116, 5], [122, 1], [171, 4], ], ), ...inlineCacheProfiles( [3505, 3506, 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3514], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 2], [116, 5], [122, 1], [171, 4], ], ), ...inlineCacheProfiles( [3515], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 5], [122, 1], [171, 4], ], ), ...inlineCacheProfiles( [3516, 3517, 3518], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 5], [122, 1], [141, 1], [171, 4], ], ), ...inlineCacheProfiles( [3519, 3520], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 5], [122, 1], [123, 1], [141, 1], [171, 4], ], ), ...inlineCacheProfiles( [3521, 3522], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [141, 1], [171, 4], ], ), ...inlineCacheProfiles( [3523], [ [25, 4], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [171, 4], ], ), ...inlineCacheProfiles( [3524], [ [25, 1], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [171, 4], ], ), ...inlineCacheProfiles( [3525, 3526, 3527], [ [25, 1], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [171, 3], ], ), ...inlineCacheProfiles( [3528, 3529], [ [25, 1], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [141, 9], [171, 3], ], ), ...inlineCacheProfiles( [3530, 3531], [ [25, 1], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [141, 1], [171, 3], ], ), ...inlineCacheProfiles( [3550, 3551, 3552, 3553, 3554, 3555, 3556, 3557, 3558], [ [25, 1], [60, 1], [92, 1], [93, 1], [95, 4], [106, 9], [107, 1], [116, 4], [122, 1], [123, 1], [140, 1], [141, 1], [171, 3], ], ), ...inlineCacheProfiles( [3559], [ [12, 1], [53, 1], [56, 3], [67, 1], [75, 3], [97, 1], [114, 1], [123, 1], [129, 9], [150, 4], [154, 1], [168, 1], [173, 4], [183, 1], ], ), ...inlineCacheProfiles( [3560], [ [12, 1], [54, 1], [57, 3], [68, 1], [76, 3], [98, 1], [115, 1], [124, 1], [130, 9], [151, 4], [155, 1], [169, 1], [174, 4], [184, 1], ], ), ...inlineCacheProfiles( [3561], [ [12, 1], [54, 1], [57, 3], [68, 1], [76, 3], [98, 1], [115, 1], [124, 1], [130, 9], [151, 4], [155, 1], [161, 1], [162, 1], [163, 1], [164, 1], [169, 1], [174, 4], [184, 1], ], ), ...inlineCacheProfiles( [3562], [ [5, 1], [39, 1], [40, 3], [45, 1], [53, 3], [57, 1], [71, 1], [76, 1], [82, 9], [91, 4], [93, 1], [97, 1], [98, 1], [99, 1], [100, 1], [104, 1], [108, 4], [117, 1], ], ), ...inlineCacheProfiles( [3563, 3564, 3565, 3566, 3567, 3568], [ [5, 1], [39, 1], [40, 3], [45, 1], [53, 3], [58, 1], [72, 1], [77, 1], [82, 9], [91, 4], [93, 1], [97, 1], [98, 1], [99, 1], [100, 1], [104, 1], [108, 4], [117, 1], ], ), ...inlineCacheProfiles( [3569, 3570], [ [5, 1], [39, 1], [40, 3], [45, 1], [53, 3], [58, 1], [59, 1], [72, 1], [77, 1], [82, 9], [91, 4], [93, 1], [97, 1], [98, 1], [99, 1], [100, 1], [104, 1], [108, 4], [117, 1], ], ), ...inlineCacheProfiles( [3571], [ [5, 1], [38, 1], [39, 3], [44, 1], [52, 3], [57, 1], [58, 1], [71, 1], [76, 1], [81, 9], [91, 4], [93, 1], [97, 1], [98, 1], [99, 1], [100, 1], [104, 1], [108, 4], [117, 1], ], ), ]); /** Preserve CPython's bytecode-size check and omit only cache code units. */ export const pythonPycBytecodeIdentity = ( bytecode: Buffer, magic: number, rawSha256: string, ): string => { const caches = INLINE_CACHE_ENTRIES_BY_MAGIC.get(magic); if (caches === undefined) return `${bytecode.length}:${rawSha256}`; const digest = createHash("sha256"); let activeStart = 0; for (let offset = 0; offset < bytecode.length;) { const opcode = bytecode[offset] ?? 0; const afterOpcode = offset + 2; const cacheBytes = (caches.get(opcode) ?? 0) * 2; offset = afterOpcode + cacheBytes; if (cacheBytes > 0) { digest.update(bytecode.subarray(activeStart, afterOpcode)); activeStart = offset; } } if (activeStart < bytecode.length) { digest.update(bytecode.subarray(activeStart)); } return `${bytecode.length}:${digest.digest("hex")}`; };