@@ -695,6 +695,239 @@ static bool runtime_posix_stat_same_image(const struct stat *first, const struct
695695
696696#endif
697697
698+ /* ── Executable-image fingerprint cache ──────────────────────────────────────
699+ * The build-identity fingerprint is a SHA-256 over the entire ~295 MB
700+ * executable (cbm_daemon_build_fingerprint_native_file) — ~2.3 s of pure CPU on
701+ * every process start and every peer rendezvous, and the direct cause of the
702+ * hook-augment startup deadline being missed on large graphs. The mapped image
703+ * is immutable for a build's lifetime, so the digest is cached keyed by the file
704+ * identity the acquire path already verifies as stable across the hash (device,
705+ * inode, size, mtime, ctime — the same tuple runtime_*_stat_same compares). A
706+ * rebuilt or replaced binary lands on a new inode/mtime and misses, so the cache
707+ * can never return a stale digest and build-cohort admission keeps its exact
708+ * meaning. A cache hit does not weaken the identity proof: the acquire chain
709+ * still brackets the held image with its before/after stat and process-maps
710+ * checks, so the returned digest is bound to that same verified image — only the
711+ * re-read of bytes whose identity is unchanged is skipped. */
712+ #if defined(_WIN32 ) || defined(__APPLE__ ) || defined(__linux__ ) || defined(__FreeBSD__ )
713+
714+ typedef struct {
715+ bool valid ;
716+ uint64_t device ;
717+ uint64_t inode ;
718+ uint64_t size ;
719+ int64_t mtime_seconds ;
720+ int64_t mtime_nanoseconds ;
721+ int64_t ctime_seconds ;
722+ int64_t ctime_nanoseconds ;
723+ } runtime_fingerprint_key_t ;
724+
725+ #define RUNTIME_FINGERPRINT_CACHE_CAP 8
726+
727+ static atomic_flag runtime_fingerprint_cache_lock = ATOMIC_FLAG_INIT ;
728+ static struct {
729+ runtime_fingerprint_key_t key ;
730+ char fingerprint [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ];
731+ } runtime_fingerprint_cache [RUNTIME_FINGERPRINT_CACHE_CAP ];
732+ static size_t runtime_fingerprint_cache_count ;
733+ static size_t runtime_fingerprint_cache_victim ;
734+
735+ #ifdef CBM_ENABLE_TEST_SEAMS
736+ static atomic_int runtime_fingerprint_hash_call_count ;
737+ static atomic_bool runtime_fingerprint_hash_stub_active ;
738+ static char runtime_fingerprint_hash_stub_digest [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ];
739+ #endif
740+
741+ static bool runtime_fingerprint_key_equal (const runtime_fingerprint_key_t * a ,
742+ const runtime_fingerprint_key_t * b ) {
743+ return a -> valid && b -> valid && a -> device == b -> device && a -> inode == b -> inode &&
744+ a -> size == b -> size && a -> mtime_seconds == b -> mtime_seconds &&
745+ a -> mtime_nanoseconds == b -> mtime_nanoseconds && a -> ctime_seconds == b -> ctime_seconds &&
746+ a -> ctime_nanoseconds == b -> ctime_nanoseconds ;
747+ }
748+
749+ static void runtime_fingerprint_cache_enter (void ) {
750+ while (
751+ atomic_flag_test_and_set_explicit (& runtime_fingerprint_cache_lock , memory_order_acquire )) {}
752+ }
753+
754+ static void runtime_fingerprint_cache_leave (void ) {
755+ atomic_flag_clear_explicit (& runtime_fingerprint_cache_lock , memory_order_release );
756+ }
757+
758+ static bool runtime_fingerprint_cache_lookup (const runtime_fingerprint_key_t * key ,
759+ char out [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
760+ if (!key -> valid ) {
761+ return false;
762+ }
763+ bool hit = false;
764+ runtime_fingerprint_cache_enter ();
765+ for (size_t index = 0 ; index < runtime_fingerprint_cache_count ; index ++ ) {
766+ if (runtime_fingerprint_key_equal (& runtime_fingerprint_cache [index ].key , key )) {
767+ memcpy (out , runtime_fingerprint_cache [index ].fingerprint ,
768+ CBM_DAEMON_BUILD_FINGERPRINT_SIZE );
769+ hit = true;
770+ break ;
771+ }
772+ }
773+ runtime_fingerprint_cache_leave ();
774+ return hit ;
775+ }
776+
777+ static void runtime_fingerprint_cache_store (
778+ const runtime_fingerprint_key_t * key ,
779+ const char fingerprint [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
780+ if (!key -> valid || fingerprint [0 ] == '\0' ) {
781+ return ;
782+ }
783+ runtime_fingerprint_cache_enter ();
784+ size_t slot = RUNTIME_FINGERPRINT_CACHE_CAP ;
785+ for (size_t index = 0 ; index < runtime_fingerprint_cache_count ; index ++ ) {
786+ if (runtime_fingerprint_key_equal (& runtime_fingerprint_cache [index ].key , key )) {
787+ slot = index ;
788+ break ;
789+ }
790+ }
791+ if (slot == RUNTIME_FINGERPRINT_CACHE_CAP ) {
792+ if (runtime_fingerprint_cache_count < RUNTIME_FINGERPRINT_CACHE_CAP ) {
793+ slot = runtime_fingerprint_cache_count ++ ;
794+ } else {
795+ slot = runtime_fingerprint_cache_victim ;
796+ runtime_fingerprint_cache_victim =
797+ (runtime_fingerprint_cache_victim + 1 ) % RUNTIME_FINGERPRINT_CACHE_CAP ;
798+ }
799+ }
800+ runtime_fingerprint_cache [slot ].key = * key ;
801+ memcpy (runtime_fingerprint_cache [slot ].fingerprint , fingerprint ,
802+ CBM_DAEMON_BUILD_FINGERPRINT_SIZE );
803+ runtime_fingerprint_cache_leave ();
804+ }
805+
806+ /* The single point where the expensive image hash is paid; a test seam counts
807+ * calls and can substitute a stub digest so the cache's hit/miss behaviour is
808+ * observable without a real 295 MB read. */
809+ static bool runtime_fingerprint_hash_native_file (uintptr_t native_file ,
810+ char out [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
811+ #ifdef CBM_ENABLE_TEST_SEAMS
812+ atomic_fetch_add_explicit (& runtime_fingerprint_hash_call_count , 1 , memory_order_relaxed );
813+ if (atomic_load_explicit (& runtime_fingerprint_hash_stub_active , memory_order_acquire )) {
814+ memcpy (out , runtime_fingerprint_hash_stub_digest , CBM_DAEMON_BUILD_FINGERPRINT_SIZE );
815+ return true;
816+ }
817+ #endif
818+ return cbm_daemon_build_fingerprint_native_file (native_file , out );
819+ }
820+
821+ /* Resolve the fingerprint of the held native image, consulting the cache before
822+ * paying for the full-image hash and populating it on a miss. */
823+ static bool runtime_build_fingerprint_cached (uintptr_t native_file ,
824+ const runtime_fingerprint_key_t * key ,
825+ char fingerprint [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
826+ if (runtime_fingerprint_cache_lookup (key , fingerprint )) {
827+ return true;
828+ }
829+ if (!runtime_fingerprint_hash_native_file (native_file , fingerprint )) {
830+ return false;
831+ }
832+ runtime_fingerprint_cache_store (key , fingerprint );
833+ return true;
834+ }
835+
836+ #ifdef _WIN32
837+ static int64_t runtime_fingerprint_filetime (FILETIME value ) {
838+ return (int64_t )(((uint64_t )value .dwHighDateTime << 32 ) | value .dwLowDateTime );
839+ }
840+
841+ static bool runtime_build_fingerprint_cached_windows (
842+ uintptr_t native_file , const BY_HANDLE_FILE_INFORMATION * information , const LARGE_INTEGER * size ,
843+ char fingerprint [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
844+ runtime_fingerprint_key_t key ;
845+ memset (& key , 0 , sizeof (key ));
846+ key .valid = information != NULL && size != NULL && size -> QuadPart >= 0 ;
847+ if (key .valid ) {
848+ key .device = information -> dwVolumeSerialNumber ;
849+ key .inode = ((uint64_t )information -> nFileIndexHigh << 32 ) | information -> nFileIndexLow ;
850+ key .size = (uint64_t )size -> QuadPart ;
851+ key .mtime_seconds = runtime_fingerprint_filetime (information -> ftLastWriteTime );
852+ key .ctime_seconds = runtime_fingerprint_filetime (information -> ftCreationTime );
853+ }
854+ return runtime_build_fingerprint_cached (native_file , & key , fingerprint );
855+ }
856+ #else
857+ static bool runtime_build_fingerprint_cached_posix (
858+ uintptr_t native_file , const struct stat * status ,
859+ char fingerprint [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
860+ runtime_fingerprint_key_t key ;
861+ memset (& key , 0 , sizeof (key ));
862+ key .valid = status != NULL && S_ISREG (status -> st_mode );
863+ if (key .valid ) {
864+ key .device = (uint64_t )status -> st_dev ;
865+ key .inode = (uint64_t )status -> st_ino ;
866+ key .size = (uint64_t )status -> st_size ;
867+ #ifdef __APPLE__
868+ key .mtime_seconds = status -> st_mtimespec .tv_sec ;
869+ key .mtime_nanoseconds = status -> st_mtimespec .tv_nsec ;
870+ key .ctime_seconds = status -> st_ctimespec .tv_sec ;
871+ key .ctime_nanoseconds = status -> st_ctimespec .tv_nsec ;
872+ #else
873+ key .mtime_seconds = status -> st_mtim .tv_sec ;
874+ key .mtime_nanoseconds = status -> st_mtim .tv_nsec ;
875+ key .ctime_seconds = status -> st_ctim .tv_sec ;
876+ key .ctime_nanoseconds = status -> st_ctim .tv_nsec ;
877+ #endif
878+ }
879+ return runtime_build_fingerprint_cached (native_file , & key , fingerprint );
880+ }
881+ #endif
882+
883+ #ifdef CBM_ENABLE_TEST_SEAMS
884+ void cbm_daemon_runtime_fingerprint_cache_reset_for_testing (void ) {
885+ runtime_fingerprint_cache_enter ();
886+ memset (runtime_fingerprint_cache , 0 , sizeof (runtime_fingerprint_cache ));
887+ runtime_fingerprint_cache_count = 0 ;
888+ runtime_fingerprint_cache_victim = 0 ;
889+ runtime_fingerprint_cache_leave ();
890+ atomic_store_explicit (& runtime_fingerprint_hash_call_count , 0 , memory_order_relaxed );
891+ atomic_store_explicit (& runtime_fingerprint_hash_stub_active , false, memory_order_release );
892+ memset (runtime_fingerprint_hash_stub_digest , 0 , sizeof (runtime_fingerprint_hash_stub_digest ));
893+ }
894+
895+ void cbm_daemon_runtime_fingerprint_cache_set_hash_stub_for_testing (const char * digest ) {
896+ if (!digest ) {
897+ atomic_store_explicit (& runtime_fingerprint_hash_stub_active , false, memory_order_release );
898+ return ;
899+ }
900+ memset (runtime_fingerprint_hash_stub_digest , 0 , sizeof (runtime_fingerprint_hash_stub_digest ));
901+ size_t length = strlen (digest );
902+ if (length >= CBM_DAEMON_BUILD_FINGERPRINT_SIZE ) {
903+ length = CBM_DAEMON_BUILD_FINGERPRINT_SIZE - 1 ;
904+ }
905+ memcpy (runtime_fingerprint_hash_stub_digest , digest , length );
906+ atomic_store_explicit (& runtime_fingerprint_hash_stub_active , true, memory_order_release );
907+ }
908+
909+ int cbm_daemon_runtime_fingerprint_hash_call_count_for_testing (void ) {
910+ return atomic_load_explicit (& runtime_fingerprint_hash_call_count , memory_order_relaxed );
911+ }
912+
913+ bool cbm_daemon_runtime_fingerprint_cache_resolve_for_testing (
914+ uint64_t device , uint64_t inode , uint64_t size , int64_t mtime_seconds ,
915+ int64_t mtime_nanoseconds , int64_t ctime_seconds , int64_t ctime_nanoseconds ,
916+ char out [CBM_DAEMON_BUILD_FINGERPRINT_SIZE ]) {
917+ runtime_fingerprint_key_t key = {.valid = true,
918+ .device = device ,
919+ .inode = inode ,
920+ .size = size ,
921+ .mtime_seconds = mtime_seconds ,
922+ .mtime_nanoseconds = mtime_nanoseconds ,
923+ .ctime_seconds = ctime_seconds ,
924+ .ctime_nanoseconds = ctime_nanoseconds };
925+ return runtime_build_fingerprint_cached ((uintptr_t )0 , & key , out );
926+ }
927+ #endif
928+
929+ #endif /* supported platform: executable-image fingerprint cache */
930+
698931/* Acquire one process instance's mapped image as a native object. Supplying a
699932 * fingerprint hashes that same held object; NULL performs metadata-only
700933 * acquisition for the HELLO fast path. Every platform brackets the process
@@ -736,7 +969,8 @@ static bool runtime_process_image_reference_acquire(
736969 : INVALID_HANDLE_VALUE ;
737970 free (open_path );
738971 ok = ok && runtime_windows_file_snapshot (file , & file_before , & size_before ) &&
739- (!fingerprint || cbm_daemon_build_fingerprint_native_file ((uintptr_t )file , fingerprint )) &&
972+ (!fingerprint || runtime_build_fingerprint_cached_windows ((uintptr_t )file , & file_before ,
973+ & size_before , fingerprint )) &&
740974 runtime_windows_file_snapshot (file , & file_after , & size_after ) &&
741975 runtime_windows_file_snapshot_same (& file_before , & size_before , & file_after , & size_after ) &&
742976 runtime_windows_process_image_snapshot (process , & process_after ) &&
@@ -772,7 +1006,8 @@ static bool runtime_process_image_reference_acquire(
7721006 struct stat file_after ;
7731007 ok = ok && fd >= 0 && fstat (fd , & file_before ) == 0 && S_ISREG (file_before .st_mode ) &&
7741008 runtime_mac_process_maps_file_executable (pid , & file_before ) &&
775- (!fingerprint || cbm_daemon_build_fingerprint_native_file ((uintptr_t )fd , fingerprint )) &&
1009+ (!fingerprint ||
1010+ runtime_build_fingerprint_cached_posix ((uintptr_t )fd , & file_before , fingerprint )) &&
7761011 fstat (fd , & file_after ) == 0 && runtime_mac_stat_same (& file_before , & file_after ) &&
7771012 runtime_mac_process_maps_file_executable (pid , & file_after ) &&
7781013 runtime_mac_process_instance (pid , & process_after ) &&
@@ -795,12 +1030,12 @@ static bool runtime_process_image_reference_acquire(
7951030 int image_fd = process_fd >= 0 ? openat (process_fd , "exe" , O_RDONLY | O_CLOEXEC ) : -1 ;
7961031 struct stat image_before ;
7971032 struct stat image_after ;
798- bool ok = image_fd >= 0 && fstat ( image_fd , & image_before ) == 0 &&
799- S_ISREG (image_before .st_mode ) &&
800- (!fingerprint ||
801- cbm_daemon_build_fingerprint_native_file ((uintptr_t )image_fd , fingerprint )) &&
802- fstat (image_fd , & image_after ) == 0 &&
803- runtime_posix_stat_same_image (& image_before , & image_after );
1033+ bool ok =
1034+ image_fd >= 0 && fstat ( image_fd , & image_before ) == 0 && S_ISREG (image_before .st_mode ) &&
1035+ (!fingerprint ||
1036+ runtime_build_fingerprint_cached_posix ((uintptr_t )image_fd , & image_before , fingerprint )) &&
1037+ fstat (image_fd , & image_after ) == 0 &&
1038+ runtime_posix_stat_same_image (& image_before , & image_after );
8041039 int verify_fd = ok ? openat (process_fd , "exe" , O_RDONLY | O_CLOEXEC ) : -1 ;
8051040 struct stat verify_status ;
8061041 ok = ok && verify_fd >= 0 && fstat (verify_fd , & verify_status ) == 0 &&
@@ -834,8 +1069,8 @@ static bool runtime_process_image_reference_acquire(
8341069 struct stat image_before ;
8351070 struct stat image_after ;
8361071 ok = image_fd >= 0 && fstat (image_fd , & image_before ) == 0 && S_ISREG (image_before .st_mode ) &&
837- (!fingerprint ||
838- cbm_daemon_build_fingerprint_native_file (( uintptr_t ) image_fd , fingerprint )) &&
1072+ (!fingerprint || runtime_build_fingerprint_cached_posix (( uintptr_t ) image_fd , & image_before ,
1073+ fingerprint )) &&
8391074 fstat (image_fd , & image_after ) == 0 &&
8401075 runtime_posix_stat_same_image (& image_before , & image_after );
8411076 if (ok ) {
0 commit comments