LCOV - code coverage report
Current view: top level - disco/topo - fd_topob.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 465 706 65.9 %
Date: 2026-09-17 04:28:31 Functions: 14 22 63.6 %

          Line data    Source code
       1             : #include "fd_topob.h"
       2             : #include "../waker/fd_waker.h"
       3             : 
       4             : #include "../../util/pod/fd_pod_format.h"
       5             : #include "../../util/tile/fd_tile_private.h" /* fd_tile_private_sibling_idx */
       6             : #include "fd_cpu_topo.h"
       7             : 
       8             : #include <ctype.h>
       9             : 
      10             : #define SET_NAME cpu_bv
      11             : #define SET_MAX  FD_TILE_MAX
      12             : #include "../../util/tmpl/fd_set.c"
      13             : 
      14             : fd_topo_t *
      15             : fd_topob_new( void * mem,
      16          66 :               char const * app_name ) {
      17          66 :   fd_topo_t * topo = (fd_topo_t *)mem;
      18             : 
      19          66 :   if( FD_UNLIKELY( !topo ) ) {
      20           0 :     FD_LOG_WARNING( ( "NULL topo" ) );
      21           0 :     return NULL;
      22           0 :   }
      23             : 
      24          66 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)topo, alignof(fd_topo_t) ) ) ) {
      25           0 :     FD_LOG_WARNING( ( "misaligned topo" ) );
      26           0 :     return NULL;
      27           0 :   }
      28             : 
      29          66 :   fd_memset( topo, 0, sizeof(fd_topo_t) );
      30             : 
      31          66 :   FD_TEST( fd_pod_new( topo->props, sizeof(topo->props) ) );
      32             : 
      33          66 :   if( FD_UNLIKELY( strlen( app_name )>=sizeof(topo->app_name) ) ) FD_LOG_ERR(( "app_name too long: %s", app_name ));
      34          66 :   fd_cstr_ncpy( topo->app_name, app_name, sizeof(topo->app_name) );
      35             : 
      36          66 :   topo->max_page_size           = FD_SHMEM_GIGANTIC_PAGE_SZ;
      37          66 :   topo->gigantic_page_threshold = 4 * FD_SHMEM_HUGE_PAGE_SZ;
      38             : 
      39          66 :   topo->sleep_obj_id = ULONG_MAX;
      40             : 
      41          66 :   topo->agave_affinity_cnt = 0;
      42          66 :   topo->blocklist_cores_cnt = 0;
      43             : 
      44          66 :   return topo;
      45          66 : }
      46             : 
      47             : fd_topo_wksp_t *
      48             : fd_topob_wksp( fd_topo_t *  topo,
      49          93 :                char const * name ) {
      50          93 :   if( FD_UNLIKELY( !topo || !name || !strlen( name ) ) ) FD_LOG_ERR(( "NULL args" ));
      51          93 :   if( FD_UNLIKELY( strlen( name )>=sizeof(topo->workspaces[ topo->wksp_cnt ].name ) ) ) FD_LOG_ERR(( "wksp name too long: %s", name ));
      52          93 :   if( FD_UNLIKELY( topo->wksp_cnt>=FD_TOPO_MAX_WKSPS ) ) FD_LOG_ERR(( "too many workspaces" ));
      53             : 
      54          93 :   fd_topo_wksp_t * wksp = &topo->workspaces[ topo->wksp_cnt ];
      55          93 :   strncpy( wksp->name, name, sizeof(wksp->name) );
      56          93 :   wksp->id = topo->wksp_cnt;
      57          93 :   wksp->core_dump_level = FD_TOPO_CORE_DUMP_LEVEL_REGULAR;
      58          93 :   topo->wksp_cnt++;
      59          93 :   return wksp;
      60          93 : }
      61             : 
      62             : fd_topo_obj_t *
      63             : fd_topob_obj( fd_topo_t *  topo,
      64             :               char const * obj_name,
      65         585 :               char const * wksp_name ) {
      66         585 :   if( FD_UNLIKELY( !topo || !obj_name || !wksp_name ) ) FD_LOG_ERR(( "NULL args" ));
      67         585 :   if( FD_UNLIKELY( strlen( obj_name )>=sizeof(topo->objs[ topo->obj_cnt ].name ) ) ) FD_LOG_ERR(( "obj name too long: %s", obj_name ));
      68         585 :   if( FD_UNLIKELY( topo->obj_cnt>=FD_TOPO_MAX_OBJS ) ) FD_LOG_ERR(( "too many objects" ));
      69             : 
      70         585 :   ulong wksp_id = fd_topo_find_wksp( topo, wksp_name );
      71         585 :   if( FD_UNLIKELY( wksp_id==ULONG_MAX ) ) FD_LOG_ERR(( "workspace not found: %s", wksp_name ));
      72             : 
      73         585 :   fd_topo_obj_t * obj = &topo->objs[ topo->obj_cnt ];
      74         585 :   memset( obj, 0, sizeof(fd_topo_obj_t) );
      75         585 :   strncpy( obj->name, obj_name, sizeof(obj->name) );
      76         585 :   obj->id        = topo->obj_cnt;
      77         585 :   obj->wksp_id   = wksp_id;
      78         585 :   obj->label_idx = ULONG_MAX;
      79         585 :   topo->obj_cnt++;
      80             : 
      81         585 :   return obj;
      82         585 : }
      83             : 
      84             : fd_topo_obj_t *
      85             : fd_topob_obj_named( fd_topo_t *  topo,
      86             :                     char const * obj_type,
      87             :                     char const * wksp_name,
      88           0 :                     char const * label ) {
      89           0 :   if( FD_UNLIKELY( !label ) ) FD_LOG_ERR(( "NULL args" ));
      90           0 :   if( FD_UNLIKELY( strlen( label )>=sizeof(topo->objs[ topo->obj_cnt ].label ) ) ) FD_LOG_ERR(( "obj label too long: %s", label ));
      91           0 :   fd_topo_obj_t * obj = fd_topob_obj( topo, obj_type, wksp_name );
      92           0 :   if( FD_UNLIKELY( !obj ) ) return NULL;
      93             : 
      94           0 :   fd_cstr_ncpy( obj->label, label, sizeof(obj->label) );
      95           0 :   obj->label_idx = fd_topo_obj_cnt( topo, obj_type, label );
      96             : 
      97           0 :   return obj;
      98           0 : }
      99             : 
     100             : fd_topo_link_t *
     101             : fd_topob_link( fd_topo_t *  topo,
     102             :                char const * link_name,
     103             :                char const * wksp_name,
     104             :                ulong        depth,
     105             :                ulong        mtu,
     106          72 :                ulong        burst ) {
     107          72 :   if( FD_UNLIKELY( !topo || !link_name || !wksp_name ) ) FD_LOG_ERR(( "NULL args" ));
     108          72 :   if( FD_UNLIKELY( strlen( link_name )>=sizeof(topo->links[ topo->link_cnt ].name ) ) ) FD_LOG_ERR(( "link name too long: %s", link_name ));
     109          72 :   if( FD_UNLIKELY( topo->link_cnt>=FD_TOPO_MAX_LINKS ) ) FD_LOG_ERR(( "too many links" ));
     110             : 
     111          72 :   ulong kind_id = 0UL;
     112         138 :   for( ulong i=0UL; i<topo->link_cnt; i++ ) {
     113          66 :     if( !strcmp( topo->links[ i ].name, link_name ) ) kind_id++;
     114          66 :   }
     115             : 
     116          72 :   fd_topo_link_t * link = &topo->links[ topo->link_cnt ];
     117          72 :   strncpy( link->name, link_name, sizeof(link->name) );
     118          72 :   link->id       = topo->link_cnt;
     119          72 :   link->kind_id  = kind_id;
     120          72 :   link->depth    = depth;
     121          72 :   link->mtu      = mtu;
     122          72 :   link->burst    = burst;
     123             : 
     124          72 :   fd_topo_obj_t * obj = fd_topob_obj( topo, "mcache", wksp_name );
     125          72 :   link->mcache_obj_id = obj->id;
     126          72 :   FD_TEST( fd_pod_insertf_ulong( topo->props, depth, "obj.%lu.depth", obj->id ) );
     127             : 
     128          72 :   if( mtu ) {
     129          36 :     obj = fd_topob_obj( topo, "dcache", wksp_name );
     130          36 :     link->dcache_obj_id = obj->id;
     131          36 :     FD_TEST( fd_pod_insertf_ulong( topo->props, depth, "obj.%lu.depth", obj->id ) );
     132          36 :     FD_TEST( fd_pod_insertf_ulong( topo->props, burst, "obj.%lu.burst", obj->id ) );
     133          36 :     FD_TEST( fd_pod_insertf_ulong( topo->props, mtu,   "obj.%lu.mtu",   obj->id ) );
     134          36 :   }
     135          72 :   topo->link_cnt++;
     136             : 
     137          72 :   return link;
     138          72 : }
     139             : 
     140             : void
     141             : fd_topob_tile_uses( fd_topo_t *           topo,
     142             :                     fd_topo_tile_t *      tile,
     143             :                     fd_topo_obj_t const * obj,
     144         666 :                     int                   mode ) {
     145         666 :   (void)topo;
     146             : 
     147         666 :   if( FD_UNLIKELY( tile->uses_obj_cnt>=FD_TOPO_MAX_TILE_OBJS ) ) FD_LOG_ERR(( "tile `%s` uses too many objects", tile->name ));
     148             : 
     149         666 :   tile->uses_obj_id[ tile->uses_obj_cnt ] = obj->id;
     150         666 :   tile->uses_obj_mode[ tile->uses_obj_cnt ] = mode;
     151         666 :   tile->uses_obj_cnt++;
     152         666 : }
     153             : 
     154             : fd_topo_tile_t *
     155             : fd_topob_tile( fd_topo_t *    topo,
     156             :                char const *   tile_name,
     157             :                char const *   tile_wksp,
     158             :                char const *   metrics_wksp,
     159             :                ulong          cpu_idx,
     160             :                int            is_agave,
     161             :                int            uses_id_keyswitch,
     162             :                int            uses_av_keyswitch,
     163          87 :                int            is_waker_client ) {
     164             : 
     165          87 :   if( FD_UNLIKELY( !topo || !tile_name || !tile_wksp || !metrics_wksp ) ) FD_LOG_ERR(( "NULL args" ));
     166          87 :   if( FD_UNLIKELY( strlen( tile_name )>=sizeof(topo->tiles[ topo->tile_cnt ].name ) ) ) FD_LOG_ERR(( "tile name too long: %s", tile_name ));
     167          87 :   if( FD_UNLIKELY( topo->tile_cnt>=FD_TOPO_MAX_TILES ) ) FD_LOG_ERR(( "too many tiles %lu", topo->tile_cnt ));
     168             : 
     169          87 :   ulong kind_id = 0UL;
     170         105 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     171          18 :     if( !strcmp( topo->tiles[ i ].name, tile_name ) ) kind_id++;
     172          18 :   }
     173             : 
     174          87 :   fd_topo_tile_t * tile = &topo->tiles[ topo->tile_cnt ];
     175          87 :   strncpy( tile->name, tile_name, sizeof(tile->name) );
     176          87 :   tile->id                  = topo->tile_cnt;
     177          87 :   tile->kind_id             = kind_id;
     178          87 :   tile->is_agave            = is_agave;
     179          87 :   tile->cpu_idx             = fd_ulong_if( cpu_idx<ULONG_MAX, cpu_idx & ~FD_TOPOB_CPU_SHARED, ULONG_MAX );
     180          87 :   tile->in_cnt              = 0UL;
     181          87 :   tile->out_cnt             = 0UL;
     182          87 :   tile->event_link_id       = ULONG_MAX;
     183          87 :   tile->uses_obj_cnt        = 0UL;
     184          87 :   tile->is_waker_client     = is_waker_client;
     185          87 :   tile->floats              = cpu_idx<ULONG_MAX && !!(cpu_idx & FD_TOPOB_CPU_SHARED);
     186          87 :   tile->waker_client_idx    = ULONG_MAX;
     187          87 :   tile->waker_fseq_obj_id   = ULONG_MAX;
     188             : 
     189          87 :   fd_topo_obj_t * tile_obj = fd_topob_obj( topo, "tile", tile_wksp );
     190          87 :   tile->tile_obj_id = tile_obj->id;
     191          87 :   fd_topob_tile_uses( topo, tile, tile_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     192             : 
     193          87 :   fd_topo_obj_t * obj = fd_topob_obj( topo, "metrics", metrics_wksp );
     194          87 :   tile->metrics_obj_id = obj->id;
     195          87 :   fd_topob_tile_uses( topo, tile, obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     196             : 
     197          87 :   if( FD_LIKELY( uses_id_keyswitch ) ) {
     198           0 :     obj = fd_topob_obj( topo, "keyswitch", tile_wksp );
     199           0 :     tile->id_keyswitch_obj_id = obj->id;
     200           0 :     fd_topob_tile_uses( topo, tile, obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     201          87 :   } else {
     202          87 :     tile->id_keyswitch_obj_id = ULONG_MAX;
     203          87 :   }
     204             : 
     205          87 :   if( FD_UNLIKELY( uses_av_keyswitch ) ) {
     206           0 :     obj = fd_topob_obj( topo, "keyswitch", tile_wksp );
     207           0 :     tile->av_keyswitch_obj_id = obj->id;
     208           0 :     fd_topob_tile_uses( topo, tile, obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     209          87 :   } else {
     210          87 :     tile->av_keyswitch_obj_id = ULONG_MAX;
     211          87 :   }
     212             : 
     213          87 :   topo->tile_cnt++;
     214          87 :   return tile;
     215          87 : }
     216             : 
     217             : void
     218           0 : fd_topob_waker( fd_topo_t * topo ) {
     219           0 :   ulong waker_client_cnt = 0UL;
     220           0 :   fd_topo_tile_t * waker_tile = NULL;
     221           0 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     222           0 :     fd_topo_tile_t * client = &topo->tiles[ i ];
     223           0 :     if( FD_LIKELY( !client->is_waker_client ) ) continue;
     224           0 :     if( FD_UNLIKELY( !waker_tile ) ) {
     225           0 :       char const * metrics_wksp = topo->workspaces[ topo->objs[ client->metrics_obj_id ].wksp_id ].name;
     226           0 :       fd_topob_wksp( topo, "waker"       );
     227           0 :       fd_topob_wksp( topo, "waker_ready" );
     228           0 :       waker_tile = fd_topob_tile( topo, "waker", "waker", metrics_wksp, ULONG_MAX, 0, 0, 0, 0 );
     229           0 :     }
     230           0 :     fd_topo_obj_t * fseq_obj = fd_topob_obj( topo, "fseq", "waker_ready" );
     231           0 :     fd_topob_tile_uses( topo, client,     fseq_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     232           0 :     fd_topob_tile_uses( topo, waker_tile, fseq_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     233           0 :     client->waker_client_idx  = waker_client_cnt++;
     234           0 :     client->waker_fseq_obj_id = fseq_obj->id;
     235           0 :   }
     236           0 :   FD_TEST( waker_client_cnt<=FD_WAKER_CLIENT_MAX );
     237           0 : }
     238             : 
     239             : void
     240             : fd_topob_tile_in( fd_topo_t *  topo,
     241             :                   char const * tile_name,
     242             :                   ulong        tile_kind_id,
     243             :                   char const * fseq_wksp,
     244             :                   char const * link_name,
     245             :                   ulong        link_kind_id,
     246             :                   int          reliable,
     247          36 :                   int          polled ) {
     248          36 :   if( FD_UNLIKELY( !topo || !tile_name || !fseq_wksp || !link_name ) ) FD_LOG_ERR(( "NULL args" ));
     249             : 
     250          36 :   ulong tile_id = fd_topo_find_tile( topo, tile_name, tile_kind_id );
     251          36 :   if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "tile not found: %s:%lu", tile_name, tile_kind_id ));
     252          36 :   fd_topo_tile_t * tile = &topo->tiles[ tile_id ];
     253             : 
     254          36 :   ulong link_id = fd_topo_find_link( topo, link_name, link_kind_id );
     255          36 :   if( FD_UNLIKELY( link_id==ULONG_MAX ) ) FD_LOG_ERR(( "link not found: %s:%lu", link_name, link_kind_id ));
     256          36 :   fd_topo_link_t * link = &topo->links[ link_id ];
     257             : 
     258          36 :   if( FD_UNLIKELY( tile->in_cnt>=FD_TOPO_MAX_TILE_IN_LINKS ) ) FD_LOG_ERR(( "too many in links: %s:%lu", tile_name, tile_kind_id ) );
     259          36 :   tile->in_link_id[ tile->in_cnt ] = link->id;
     260          36 :   tile->in_link_reliable[ tile->in_cnt ] = reliable;
     261          36 :   tile->in_link_poll[ tile->in_cnt ] = polled;
     262          36 :   fd_topo_obj_t * obj = fd_topob_obj( topo, "fseq", fseq_wksp );
     263          36 :   fd_topob_tile_uses( topo, tile, obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
     264          36 :   tile->in_link_fseq_obj_id[ tile->in_cnt ] = obj->id;
     265          36 :   tile->in_cnt++;
     266             : 
     267          36 :   fd_topob_tile_uses( topo, tile, &topo->objs[ link->mcache_obj_id ], FD_SHMEM_JOIN_MODE_READ_ONLY );
     268          36 :   if( FD_LIKELY( link->mtu ) ) {
     269          24 :     fd_topob_tile_uses( topo, tile, &topo->objs[ link->dcache_obj_id ], FD_SHMEM_JOIN_MODE_READ_ONLY );
     270          24 :   }
     271          36 : }
     272             : 
     273             : void
     274             : fd_topob_tile_out( fd_topo_t *  topo,
     275             :                    char const * tile_name,
     276             :                    ulong        tile_kind_id,
     277             :                    char const * link_name,
     278         198 :                    ulong        link_kind_id ) {
     279         198 :   ulong tile_id = fd_topo_find_tile( topo, tile_name, tile_kind_id );
     280         198 :   if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "tile not found: %s:%lu", tile_name, tile_kind_id ));
     281         198 :   fd_topo_tile_t * tile = &topo->tiles[ tile_id ];
     282             : 
     283         198 :   ulong link_id = fd_topo_find_link( topo, link_name, link_kind_id );
     284         198 :   if( FD_UNLIKELY( link_id==ULONG_MAX ) ) FD_LOG_ERR(( "link not found: %s:%lu", link_name, link_kind_id ));
     285         198 :   fd_topo_link_t * link = &topo->links[ link_id ];
     286             : 
     287         198 :   if( FD_UNLIKELY( tile->out_cnt>=FD_TOPO_MAX_TILE_OUT_LINKS ) ) FD_LOG_ERR(( "too many out links: %s", tile_name ));
     288         198 :   tile->out_link_id[ tile->out_cnt ] = link->id;
     289         198 :   tile->out_cnt++;
     290             : 
     291         198 :   fd_topob_tile_uses( topo, tile, &topo->objs[ link->mcache_obj_id ], FD_SHMEM_JOIN_MODE_READ_WRITE );
     292         198 :   if( FD_LIKELY( link->mtu ) ) {
     293         174 :     fd_topob_tile_uses( topo, tile, &topo->objs[ link->dcache_obj_id ], FD_SHMEM_JOIN_MODE_READ_WRITE );
     294         174 :   }
     295         198 : }
     296             : 
     297             : static void
     298          24 : validate( fd_topo_t const * topo ) {
     299             :   /* Objects have valid wksp_ids */
     300          96 :   for( ulong i=0UL; i<topo->obj_cnt; i++ ) {
     301          72 :     if( FD_UNLIKELY( topo->objs[ i ].wksp_id>=topo->wksp_cnt ) )
     302           0 :       FD_LOG_ERR(( "invalid workspace id %lu", topo->objs[ i ].wksp_id ));
     303          72 :   }
     304             : 
     305             :   /* Tile ins are valid */
     306          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     307          24 :     for( ulong j=0UL; j<topo->tiles[ i ].in_cnt; j++ ) {
     308           0 :       if( FD_UNLIKELY( topo->tiles[ i ].in_link_id[ j ]>=topo->link_cnt ) )
     309           0 :         FD_LOG_ERR(( "tile %lu (%s) has invalid in link %lu", i, topo->tiles[ i ].name, topo->tiles[ i ].in_link_id[ j ] ));
     310           0 :     }
     311          24 :   }
     312             : 
     313             :   /* Tile does not have duplicated ins */
     314          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     315          24 :     for( ulong j=0UL; j<topo->tiles[ i ].in_cnt; j++ ) {
     316           0 :       for( ulong k=0UL; k<topo->tiles[ i ].in_cnt; k++ ) {
     317           0 :         if( FD_UNLIKELY( j==k ) ) continue;
     318           0 :         if( FD_UNLIKELY( topo->tiles[ i ].in_link_id[ j ] == topo->tiles[ i ].in_link_id[ k ] ) )
     319           0 :           FD_LOG_ERR(( "tile %lu (%s) has duplicated in link %lu (%s)", i, topo->tiles[ i ].name,
     320           0 :               topo->tiles[ i ].in_link_id[ j ], topo->links[ topo->tiles[ i ].in_link_id[ j ] ].name ));
     321           0 :       }
     322           0 :     }
     323          24 :   }
     324             : 
     325             :   /* Tile does not have duplicated outs */
     326          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     327          24 :     for( ulong j=0UL; j<topo->tiles[ i ].out_cnt; j++ ) {
     328           0 :       for( ulong k=0UL; k<topo->tiles[ i ].out_cnt; k++ ) {
     329           0 :         if( FD_UNLIKELY( j==k ) ) continue;
     330           0 :         if( FD_UNLIKELY( topo->tiles[ i ].out_link_id[ j ] == topo->tiles[ i ].out_link_id[ k ] ) )
     331           0 :           FD_LOG_ERR(( "tile %lu (%s) has duplicated out link %lu (%s)", i, topo->tiles[ i ].name,
     332           0 :               topo->tiles[ i ].out_link_id[ j ], topo->links[ topo->tiles[ i ].out_link_id[ j ] ].name ));
     333           0 :       }
     334           0 :     }
     335          24 :   }
     336             : 
     337             :   /* Tile outs are different than ins */
     338          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     339          24 :     for( ulong j=0UL; j<topo->tiles[ i ].out_cnt; j++ ) {
     340           0 :       for( ulong k=0UL; k<topo->tiles[ i ].in_cnt; k++ ) {
     341           0 :         char const * link_name = topo->links[ topo->tiles[ i ].out_link_id[ j ] ].name;
     342             :         /* PoH tile "publishes" this on behalf of Agave, so it's not
     343             :            a real circular link. */
     344           0 :         if( FD_UNLIKELY( !strcmp( link_name, "stake_out" ) ||
     345           0 :                          !strcmp( link_name, "crds_shred" ) ) ) continue;
     346             : 
     347           0 :         if( FD_UNLIKELY( topo->tiles[ i ].out_link_id[ j ] == topo->tiles[ i ].in_link_id[ k ] ) )
     348           0 :           FD_LOG_ERR(( "tile %lu has out link %lu same as in", i, topo->tiles[ i ].out_link_id[ j ] ));
     349           0 :       }
     350           0 :     }
     351          24 :   }
     352             : 
     353             :   /* Non polling tile ins are also not reliable */
     354          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     355          24 :     for( ulong j=0UL; j<topo->tiles[ i ].in_cnt; j++ ) {
     356           0 :       if( FD_UNLIKELY( !topo->tiles[ i ].in_link_poll[ j ] && topo->tiles[ i ].in_link_reliable[ j ] ) )
     357           0 :         FD_LOG_ERR(( "tile %lu has in link %lu which is not polled but reliable", i, topo->tiles[ i ].in_link_id[ j ] ));
     358           0 :     }
     359          24 :   }
     360             : 
     361             :   /* Tile outs are valid */
     362          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     363          24 :     for( ulong j=0UL; j<topo->tiles[ i ].out_cnt; j++ ) {
     364           0 :       if( FD_UNLIKELY( topo->tiles[ i ].out_link_id[ j ] >= topo->link_cnt ) )
     365           0 :         FD_LOG_ERR(( "tile %lu has invalid out link %lu", i, topo->tiles[ i ].out_link_id[ j ] ));
     366           0 :     }
     367          24 :   }
     368             : 
     369             :   /* Waker clients are wired */
     370          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     371          24 :     fd_topo_tile_t const * tile = &topo->tiles[ i ];
     372          24 :     if( FD_UNLIKELY( tile->is_waker_client && tile->waker_client_idx==ULONG_MAX ) )
     373           0 :       FD_LOG_ERR(( "tile %s:%lu is a waker client but fd_topob_waker was not called", tile->name, tile->kind_id ));
     374          24 :   }
     375             : 
     376             :   /* Floating tiles have a CPU: it places their memory and anchors
     377             :      their affinity mask */
     378          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     379          24 :     fd_topo_tile_t const * tile = &topo->tiles[ i ];
     380          24 :     if( FD_UNLIKELY( tile->floats && tile->cpu_idx>=FD_TILE_MAX ) )
     381           0 :       FD_LOG_ERR(( "tile %s:%lu floats but has no CPU", tile->name, tile->kind_id ));
     382          24 :   }
     383             : 
     384             :   /* Workspace names are unique */
     385          72 :   for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
     386         144 :     for( ulong j=0UL; j<topo->wksp_cnt; j++ ) {
     387          96 :       if( FD_UNLIKELY( i==j ) ) continue;
     388          48 :       if( FD_UNLIKELY( !strcmp( topo->workspaces[ i ].name, topo->workspaces[ j ].name ) ) )
     389           0 :         FD_LOG_ERR(( "duplicate workspace name %s", topo->workspaces[ i ].name ));
     390          48 :     }
     391          48 :   }
     392             : 
     393             :   /* Each workspace is identified correctly */
     394          72 :   for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
     395          48 :     if( FD_UNLIKELY( topo->workspaces[ i ].id != i ) )
     396           0 :       FD_LOG_ERR(( "workspace %lu has id %lu", i, topo->workspaces[ i ].id ));
     397          48 :   }
     398             : 
     399             :   /* Each link has exactly one producer */
     400          24 :   for( ulong i=0UL; i<topo->link_cnt; i++ ) {
     401           0 :     ulong producer_cnt = 0;
     402           0 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     403           0 :       for( ulong k=0UL; k<topo->tiles[ j ].out_cnt; k++ ) {
     404           0 :         if( topo->tiles[ j ].out_link_id[ k ]==i ) producer_cnt++;
     405           0 :       }
     406           0 :     }
     407           0 :     if( FD_UNLIKELY( producer_cnt>1UL || ( producer_cnt==0UL && !topo->links[ i ].permit_no_producers ) ) )
     408           0 :       FD_LOG_ERR(( "link %lu (%s:%lu) has %lu producers", i, topo->links[ i ].name, topo->links[ i ].kind_id, producer_cnt ));
     409           0 :   }
     410             : 
     411             :   /* Each link has at least one consumer */
     412          24 :   for( ulong i=0UL; i<topo->link_cnt; i++ ) {
     413           0 :     ulong cnt = fd_topo_link_consumer_cnt( topo, &topo->links[ i ] );
     414           0 :     if( FD_UNLIKELY( cnt < 1UL && !topo->links[ i ].permit_no_consumers ) ) {
     415           0 :       FD_LOG_ERR(( "link %lu (%s:%lu) has 0 consumers", i, topo->links[ i ].name, topo->links[ i ].kind_id ));
     416           0 :     }
     417           0 :   }
     418          24 : }
     419             : 
     420             : /* Tiles that yield to the kernel scheduler */
     421             : static char const * FLOATING[] = {
     422             :   "netlnk",
     423             :   "metric",
     424             :   "diag",
     425             :   "waker",
     426             :   "bencho",
     427             :   "genesi", /* FIREDANCER ONLY */
     428             :   "ipecho", /* FIREDANCER ONLY */
     429             :   "admin",  /* FIREDANCER ONLY */
     430             :   "snapsv", /* FIREDANCER ONLY */
     431             :   "snapmk", /* FIREDANCER ONLY */
     432             :   "snapzp", /* FIREDANCER ONLY */
     433             :   "snaprd", /* FIREDANCER ONLY */
     434             :   NULL
     435             : };
     436             : 
     437             : /* Tiles only active on startup
     438             :    (Must shut down after snapshot load) */
     439             : static char const * STARTUP[] = {
     440             :   "genesi", /* FIREDANCER only */
     441             :   "snapct", /* FIREDANCER only */
     442             :   "snapld", /* FIREDANCER only */
     443             :   "snapdc", /* FIREDANCER only */
     444             :   "snapin", /* FIREDANCER only */
     445             :   "snapwr", /* FIREDANCER only */
     446             :   NULL
     447             : };
     448             : 
     449             : /* Tiles only active post startup
     450             :    (Must sleep until snapshot load finishes) */
     451             : static char const * POST_START[] = {
     452             :   "accdb",  /* FIREDANCER only */
     453             :   "execle", /* FIREDANCER only */
     454             :   "poh",    /* FIREDANCER only */
     455             :   "motor",  /* FIREDANCER only */
     456             :   "execrp", /* FIREDANCER only */
     457             :   "txsend", /* FIREDANCER only */
     458             :   "solcap", /* FIREDANCER only */
     459             :   NULL
     460             : };
     461             : 
     462             : /* Tiles that are always active */
     463             : static char const * ALWAYS[] = {
     464             :   "backt",
     465             :   "benchg",
     466             :   "benchs",
     467             :   "net",
     468             :   "mlx5",
     469             :   "sock",
     470             :   "quic",
     471             :   "bundle",
     472             :   "verify",
     473             :   "dedup",
     474             :   "resolh", /* FRANK only */
     475             :   "resolv", /* FIREDANCER only */
     476             :   "pack",
     477             :   "bank",   /* FRANK only */
     478             :   "pohh",   /* FRANK only */
     479             :   "sign",
     480             :   "shred",
     481             :   "event",  /* FIREDANCER only */
     482             :   "store",  /* FRANK only */
     483             :   "plugin", /* FRANK only */
     484             :   "gui",    /* FIREDANCER only */
     485             :   "guih",   /* FRANK only */
     486             :   "rpc",    /* FIREDANCER only */
     487             :   "gossvf", /* FIREDANCER only */
     488             :   "gossip", /* FIREDANCER only */
     489             :   "repair", /* FIREDANCER only */
     490             :   "rotor",  /* FIREDANCER only */
     491             :   "rserve", /* FIREDANCER only */
     492             :   "replay", /* FIREDANCER only */
     493             :   "tower",  /* FIREDANCER only */
     494             :   "votor",  /* FIREDANCER only */
     495             :   "pktgen",
     496             :   "forkt",  /* FIREDANCER only */
     497             :   NULL
     498             : };
     499             : 
     500             : /* Tiles that should not have a SMT neighbor */
     501             : static char const * CRITICAL_TILES[] = {
     502             :   "pack",
     503             :   "poh",
     504             :   "motor",
     505             :   "pohh",
     506             :   NULL
     507             : };
     508             : 
     509             : int
     510           0 : fd_topob_tile_priority_type( char const * name ) {
     511           0 :   for( char const ** p = FLOATING; *p; p++ ) {
     512           0 :     if( !strcmp( name, *p ) ) return FD_TOPOB_PRIORITY_FLOATING;
     513           0 :   }
     514           0 :   for( char const ** p = STARTUP; *p; p++ ) {
     515           0 :     if( !strcmp( name, *p ) ) return FD_TOPOB_PRIORITY_STARTUP;
     516           0 :   }
     517           0 :   for( char const ** p = CRITICAL_TILES; *p; p++ ) {
     518           0 :     if( !strcmp( name, *p ) ) return FD_TOPOB_PRIORITY_CRITICAL;
     519           0 :   }
     520           0 :   for( char const ** p = POST_START; *p; p++ ) {
     521           0 :     if( !strcmp( name, *p ) ) return FD_TOPOB_PRIORITY_NORMAL;
     522           0 :   }
     523           0 :   for( char const ** p = ALWAYS; *p; p++ ) {
     524           0 :     if( !strcmp( name, *p ) ) return FD_TOPOB_PRIORITY_NORMAL;
     525           0 :   }
     526           0 :   return FD_TOPOB_PRIORITY_FLOATING;
     527           0 : }
     528             : 
     529             : FD_STATIC_ASSERT( FD_TILE_MAX<FD_TOPOB_CPU_SHARED, update_tile_to_cpu_type );
     530             : 
     531             : ulong
     532             : fd_topob_parse_affinity_cstr( char const * cstr,
     533             :                               ushort *     tile_to_cpu,
     534             :                               int          allow_repeats,
     535           0 :                               int          allow_shared ) {
     536           0 :   if( !cstr ) return 0UL;
     537           0 :   ulong cnt = 0UL;
     538             : 
     539           0 :   cpu_bv_t cpu_assigned[ cpu_bv_word_cnt ];
     540           0 :   cpu_bv_new( cpu_assigned );
     541             : 
     542           0 :   char const * p = cstr;
     543           0 :   for(;;) {
     544             : 
     545           0 :     while( fd_isspace( (int)p[0] ) ) p++;
     546             : 
     547           0 :     if( p[0]=='f' ) {
     548           0 :       p++;
     549             : 
     550           0 :       ulong float_cnt;
     551             : 
     552           0 :       while( fd_isspace( (int)p[0] ) ) p++;
     553           0 :       if     ( p[0]==','             ) float_cnt = 1UL, p++;
     554           0 :       else if( p[0]=='\0'            ) float_cnt = 1UL;
     555           0 :       else if( !fd_isdigit( (int)p[0] ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (malformed float count)" ));
     556           0 :       else {
     557           0 :         float_cnt = fd_cstr_to_ulong( p );
     558           0 :         if( FD_UNLIKELY( !float_cnt ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (bad float count)" ));
     559           0 :         p++; while( fd_isdigit( (int)p[0] ) ) p++;
     560           0 :         while( fd_isspace( (int)p[0] ) ) p++;
     561           0 :         if( FD_UNLIKELY( !( p[0]==',' || p[0]=='\0' ) ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (bad float count delimiter)" ));
     562           0 :         if( p[0]==',' ) p++;
     563           0 :       }
     564             : 
     565           0 :       do {
     566           0 :         if( FD_UNLIKELY( cnt>=FD_TILE_MAX ) ) FD_LOG_ERR(( "fd_topob: too many affinity entries" ));
     567           0 :         tile_to_cpu[ cnt++ ] = (ushort)65535;
     568           0 :       } while( --float_cnt );
     569             : 
     570           0 :       continue;
     571           0 :     }
     572             : 
     573           0 :     ulong shared = 0UL;
     574           0 :     if( p[0]=='s' ) {
     575           0 :       if( FD_UNLIKELY( !allow_shared ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (shared cpus not supported here)" ));
     576           0 :       p++; shared = FD_TOPOB_CPU_SHARED;
     577           0 :     }
     578             : 
     579           0 :     if( !fd_isdigit( (int)p[0] ) ) {
     580           0 :       if( FD_UNLIKELY( shared || p[0]!='\0' ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (range lo not a cpu)" ));
     581           0 :       break;
     582           0 :     }
     583           0 :     ulong cpu0   = fd_cstr_to_ulong( p );
     584           0 :     ulong cpu1   = cpu0;
     585           0 :     ulong stride = 1UL;
     586           0 :     p++; while( fd_isdigit( (int)p[0] ) ) p++;
     587           0 :     while( fd_isspace( (int)p[0] ) ) p++;
     588           0 :     if( p[0]=='-' ) {
     589           0 :       p++;
     590           0 :       while( fd_isspace( (int)p[0] ) ) p++;
     591           0 :       if( FD_UNLIKELY( !fd_isdigit( (int)p[0] ) ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (range hi not a cpu)" ));
     592           0 :       cpu1 = fd_cstr_to_ulong( p );
     593           0 :       p++; while( fd_isdigit( (int)p[0] ) ) p++;
     594           0 :       while( fd_isspace( (int)p[0] ) ) p++;
     595           0 :       if( p[0]=='/' || p[0]==':' ) {
     596           0 :         p++;
     597           0 :         while( fd_isspace( (int)p[0] ) ) p++;
     598           0 :         if( FD_UNLIKELY( !fd_isdigit( (int)p[0] ) ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (stride not an int)" ));
     599           0 :         stride = fd_cstr_to_ulong( p );
     600           0 :         p++; while( fd_isdigit( (int)p[0] ) ) p++;
     601           0 :       }
     602           0 :     }
     603           0 :     else if( p[0]=='h' ) {
     604           0 :       p++;
     605           0 :       ulong sibling = fd_tile_private_sibling_idx( cpu0 );
     606           0 :       cpu1 =   fd_ulong_if( sibling==ULONG_MAX, cpu0, sibling );
     607           0 :       stride = fd_ulong_if( sibling==ULONG_MAX, 1,    sibling-cpu0 );
     608           0 :     }
     609           0 :     while( fd_isspace( (int)p[0] ) ) p++;
     610           0 :     if( FD_UNLIKELY( !( p[0]==',' || p[0]=='\0' ) ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (bad range delimiter)" ));
     611           0 :     if( p[0]==',' ) p++;
     612           0 :     cpu1++;
     613           0 :     if( FD_UNLIKELY( cpu1<=cpu0 ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (invalid range)"  ));
     614           0 :     if( FD_UNLIKELY( !stride    ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (invalid stride)" ));
     615             : 
     616           0 :     for( ulong cpu=cpu0; cpu<cpu1; cpu+=stride ) {
     617           0 :       if( FD_UNLIKELY( cnt>=FD_TILE_MAX ) ) FD_LOG_ERR(( "fd_topob: too many affinity entries" ));
     618           0 :       if( FD_UNLIKELY( cpu>=FD_TILE_MAX ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (cpu index too large)" )); /* cpu_assigned holds FD_TILE_MAX */
     619           0 :       if( FD_UNLIKELY( !allow_repeats && !shared && cpu_bv_test( cpu_assigned, cpu ) ) ) FD_LOG_ERR(( "fd_topob: malformed affinity string (repeated cpu)" ));
     620           0 :       tile_to_cpu[ cnt++ ] = (ushort)(cpu | shared);
     621           0 :       if( !shared ) cpu_bv_insert( cpu_assigned, cpu );
     622           0 :     }
     623           0 :   }
     624             : 
     625           0 :   return cnt;
     626           0 : }
     627             : 
     628             : static int
     629             : tile_name_in( char const *         name,
     630           0 :               char const * const * names ) {
     631           0 :   for( char const * const * p = names; *p; p++ ) {
     632           0 :     if( !strcmp( name, *p ) ) return 1;
     633           0 :   }
     634           0 :   return 0;
     635           0 : }
     636             : 
     637           0 : #define FD_TOPOB_LIVE_ALWAYS    (1)
     638           0 : #define FD_TOPOB_LIVE_STARTUP   (2)
     639           0 : #define FD_TOPOB_LIVE_POSTSTART (3)
     640             : 
     641             : static int
     642           0 : fd_topob_tile_live_phase( char const * name ) {
     643           0 :   if( tile_name_in( name, STARTUP    ) ) return FD_TOPOB_LIVE_STARTUP;
     644           0 :   if( tile_name_in( name, POST_START ) ) return FD_TOPOB_LIVE_POSTSTART;
     645           0 :   return FD_TOPOB_LIVE_ALWAYS;
     646           0 : }
     647             : 
     648             : static int
     649             : fd_topob_cpu_overlap_allowed( fd_topo_tile_t const * a,
     650           0 :                               fd_topo_tile_t const * b ) {
     651           0 :   if( a->floats && b->floats ) return 1;
     652             : 
     653           0 :   int a_phase = fd_topob_tile_live_phase( a->name );
     654           0 :   int b_phase = fd_topob_tile_live_phase( b->name );
     655             : 
     656           0 :   return ( a_phase==FD_TOPOB_LIVE_STARTUP   && b_phase==FD_TOPOB_LIVE_POSTSTART ) ||
     657           0 :          ( a_phase==FD_TOPOB_LIVE_POSTSTART && b_phase==FD_TOPOB_LIVE_STARTUP   );
     658           0 : }
     659             : 
     660             : void
     661          24 : fd_topob_validate_cpu_overlaps( fd_topo_t const * topo ) {
     662          48 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     663          24 :     fd_topo_tile_t const * a = &topo->tiles[ i ];
     664          24 :     if( a->cpu_idx>=FD_TILE_MAX ) continue;
     665             : 
     666          24 :     for( ulong j=i+1UL; j<topo->tile_cnt; j++ ) {
     667           0 :       fd_topo_tile_t const * b = &topo->tiles[ j ];
     668           0 :       if( b->cpu_idx!=a->cpu_idx ) continue;
     669           0 :       if( FD_LIKELY( fd_topob_cpu_overlap_allowed( a, b ) ) ) continue;
     670             : 
     671           0 :       FD_LOG_ERR(( "tile `%s:%lu` and tile `%s:%lu` are both assigned to CPU %lu and may try to run at the same time",
     672           0 :                    a->name, a->kind_id, b->name, b->kind_id, a->cpu_idx ));
     673           0 :     }
     674          24 :   }
     675          24 : }
     676             : 
     677             : static void
     678             : auto_tile_cpu( fd_topo_tile_t * tile,
     679             :                fd_topo_cpus_t * cpus,
     680             :                ulong *          cpu_idx_p,
     681             :                cpu_bv_t         cpu_assigned[ static cpu_bv_word_cnt ],
     682             :                ushort const     cpu_ordering[ static FD_TILE_MAX     ],
     683         174 :               _Bool             skip_ht_pairs ) {
     684         174 :   ulong cpu_idx = *cpu_idx_p;
     685             : 
     686         174 :   ulong cpu_cnt = cpus->cpu_cnt;
     687         522 :   while( cpu_idx<cpu_cnt && cpu_bv_test( cpu_assigned, cpu_ordering[ cpu_idx ] ) ) cpu_idx++;
     688         174 :   if( FD_UNLIKELY( cpu_idx>=cpu_cnt ) ) {
     689           0 :     FD_LOG_ERR(( "auto layout cannot set affinity for tile `%s:%lu` because all the CPUs are already assigned", tile->name, tile->kind_id ));
     690           0 :   }
     691             : 
     692             :   /* Certain tiles are latency and throughput critical and
     693             :      should not get a HT pair assigned. */
     694         174 :   fd_topo_cpu_t const * cpu = &cpus->cpu[ cpu_ordering[ cpu_idx ] ];
     695             : 
     696         174 :   int is_ht_critical = 0;
     697         174 :   if( FD_UNLIKELY( cpu->sibling!=ULONG_MAX ) ) {
     698         834 :     for( char const ** p = CRITICAL_TILES; *p; p++ ) {
     699         672 :       if( !strcmp( tile->name, *p ) ) {
     700          12 :         is_ht_critical = 1;
     701          12 :         break;
     702          12 :       }
     703         672 :     }
     704         174 :   }
     705             : 
     706         174 :   if( FD_UNLIKELY( is_ht_critical || skip_ht_pairs ) ) {
     707         174 :     ulong try_assign = cpu_idx;
     708         174 :     while( cpu_bv_test( cpu_assigned, cpu_ordering[ try_assign ] ) ||
     709         174 :            ( cpus->cpu[ cpu_ordering[ try_assign ] ].sibling!=ULONG_MAX &&
     710         174 :              cpu_bv_test( cpu_assigned, cpus->cpu[ cpu_ordering[ try_assign ] ].sibling ) ) ) {
     711           0 :       try_assign++;
     712           0 :       if( FD_UNLIKELY( try_assign>=cpus->cpu_cnt ) ) FD_LOG_ERR(( "auto layout cannot set affinity for tile `%s:%lu` because all the CPUs are already assigned or have a HT pair assigned", tile->name, tile->kind_id ));
     713           0 :     }
     714             : 
     715         174 :     ulong sibling = cpus->cpu[ cpu_ordering[ try_assign ] ].sibling;
     716         174 :     cpu_bv_insert( cpu_assigned, cpu_ordering[ try_assign ] );
     717         174 :     if( sibling!=ULONG_MAX ) {
     718         174 :       cpu_bv_insert( cpu_assigned, sibling );
     719         174 :     }
     720         174 :     tile->cpu_idx = cpu_ordering[ try_assign ];
     721         174 :   } else {
     722           0 :     cpu_bv_insert( cpu_assigned, cpu_ordering[ cpu_idx ] );
     723           0 :     tile->cpu_idx = cpu_ordering[ cpu_idx ];
     724           0 :   }
     725             : 
     726         174 :   *cpu_idx_p = cpu_idx;
     727         174 : }
     728             : 
     729             : void
     730             : fd_topob_auto_layout_cpus( fd_topo_t *      topo,
     731             :                            fd_topo_cpus_t * cpus,
     732           6 :                            int              reserve_agave_cores ) {
     733             :   /* Incredibly simple automatic layout system for now ... just assign
     734             :      tiles to CPU cores in NUMA sequential order, except for a few tiles
     735             :      which should be floating. */
     736             : 
     737         204 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     738         198 :     fd_topo_tile_t * tile = &topo->tiles[ i ];
     739         198 :     tile->cpu_idx = ULONG_MAX;
     740         198 :   }
     741             : 
     742           6 :   ushort   cpu_ordering[ FD_TILE_MAX ] = {0};
     743           6 :   cpu_bv_t pairs_assigned[ cpu_bv_word_cnt ]; cpu_bv_new( pairs_assigned );
     744           6 :   FD_STATIC_ASSERT( FD_TILE_MAX<=USHORT_MAX, layout );
     745             : 
     746           6 :   ulong next_cpu_idx = 0UL;
     747          12 :   for( ulong i=0UL; i<cpus->numa_node_cnt; i++ ) {
     748        1542 :     for( ulong j=0UL; j<cpus->cpu_cnt; j++ ) {
     749        1536 :       fd_topo_cpu_t * cpu = &cpus->cpu[ j ];
     750             : 
     751        1536 :       if( FD_UNLIKELY( cpu_bv_test( pairs_assigned, j ) || cpu->numa_node!=i ) ) continue;
     752             : 
     753         768 :       FD_TEST( next_cpu_idx<FD_TILE_MAX );
     754         768 :       cpu_ordering[ next_cpu_idx++ ] = (ushort)j;
     755             : 
     756         768 :       if( FD_UNLIKELY( cpu->sibling!=ULONG_MAX ) ) {
     757             :         /* If the CPU has a HT pair, place it immediately after so they
     758             :            are sequentially assigned. */
     759         768 :         FD_TEST( next_cpu_idx<FD_TILE_MAX );
     760         768 :         cpu_ordering[ next_cpu_idx++ ] = (ushort)cpu->sibling;
     761         768 :         cpu_bv_insert( pairs_assigned, cpu->sibling );
     762         768 :       }
     763         768 :     }
     764           6 :   }
     765             : 
     766           6 :   FD_TEST( next_cpu_idx==cpus->cpu_cnt );
     767             : 
     768             :   /* excluded cpus are simply considered already assigned */
     769           6 :   cpu_bv_t cpu_assigned[ cpu_bv_word_cnt ];
     770           6 :   cpu_bv_new( cpu_assigned );
     771          18 :   for( ulong i=0UL; i<topo->blocklist_cores_cnt; i++ ) {
     772          12 :     FD_TEST( topo->blocklist_cores_cpu_idx[ i ]<FD_TILE_MAX );
     773          12 :     cpu_bv_insert( cpu_assigned, topo->blocklist_cores_cpu_idx[ i ] );
     774          12 :   }
     775             : 
     776             :   /* Compute total number of available physical cores */
     777           6 :   ulong available_physical = 0UL;
     778        1542 :   for( ulong i=0UL; i<cpus->cpu_cnt; i++ ) {
     779        1536 :     if( !cpu_bv_test( cpu_assigned, i   ) &&
     780        1536 :         !cpu_bv_test( pairs_assigned, i ) &&
     781        1536 :         ( cpus->cpu[ i ].sibling==ULONG_MAX ||
     782         762 :           !cpu_bv_test( cpu_assigned, cpus->cpu[ i ].sibling ) ) ) {
     783         762 :       available_physical++;
     784         762 :     }
     785        1536 :   }
     786             : 
     787             :   /* Compute total number of tiles that need assignment */
     788           6 :   ulong always_tiles_to_assign = 0UL;
     789           6 :   ulong post_start_tiles_to_assign = 0UL;
     790           6 :   ulong startup_tiles_to_assign = 0UL;
     791         204 :   for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     792        1437 :     for( char const ** p = POST_START; *p; p++ ) {
     793        1281 :       if( !strcmp( topo->tiles[ j ].name, *p ) ) {
     794          42 :         post_start_tiles_to_assign++;
     795          42 :         break;
     796          42 :       }
     797        1281 :     }
     798        4134 :     for( char const ** p = ALWAYS; *p; p++ ) {
     799        4065 :       if( !strcmp( topo->tiles[ j ].name, *p ) ) {
     800         129 :         always_tiles_to_assign++;
     801         129 :         break;
     802         129 :       }
     803        4065 :     }
     804        1368 :     for( char const ** p = STARTUP; *p; p++ ) {
     805        1173 :       if( !strcmp( topo->tiles[ j ].name, *p ) ) {
     806           3 :         startup_tiles_to_assign++;
     807           3 :         break;
     808           3 :       }
     809        1173 :     }
     810         198 :   }
     811           6 :   ulong tiles_to_assign = always_tiles_to_assign +
     812           6 :       fd_ulong_max( startup_tiles_to_assign, post_start_tiles_to_assign );
     813             : 
     814             :   /* If we have enough physical cores (excluding HT siblings) for all
     815             :      tiles that need assignment, exclude HT siblings so that no tile
     816             :      gets scheduled on a hyperthread pair.
     817             :      For Frankendancer, we reserve 2x cores so we have enough for Agave */
     818           6 :   _Bool skip_ht_pairs = reserve_agave_cores
     819           6 :     ? (available_physical>=2*tiles_to_assign) /* Frankendancer */
     820           6 :     : (available_physical>=tiles_to_assign);  /* Firedancer */
     821             : 
     822             :   /* First, assign always-on tiles */
     823           6 :   ulong cpu_idx = 0UL;
     824         204 :   for( char const ** p = ALWAYS; *p; p++ ) {
     825        6732 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     826        6534 :       fd_topo_tile_t * tile = &topo->tiles[ j ];
     827        6534 :       if( !strcmp( tile->name, *p ) ) {
     828         129 :         auto_tile_cpu( tile, cpus, &cpu_idx, cpu_assigned, cpu_ordering, skip_ht_pairs );
     829         129 :       }
     830        6534 :     }
     831         198 :   }
     832           6 :   ulong cpu_idx_startup = cpu_idx;
     833           6 :   cpu_bv_t cpu_assigned_startup[ cpu_bv_word_cnt ];
     834           6 :   cpu_bv_copy( cpu_assigned_startup, cpu_assigned );
     835             : 
     836             :   /* Separately assign startup and post-start tiles */
     837          42 :   for( char const ** p = STARTUP; *p; p++ ) {
     838        1224 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     839        1188 :       fd_topo_tile_t * tile = &topo->tiles[ j ];
     840        1188 :       if( !strcmp( tile->name, *p ) ) {
     841           3 :         auto_tile_cpu( tile, cpus, &cpu_idx_startup, cpu_assigned_startup, cpu_ordering, skip_ht_pairs );
     842           3 :       }
     843        1188 :     }
     844          36 :   }
     845          48 :   for( char const ** p = POST_START; *p; p++ ) {
     846        1428 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     847        1386 :       fd_topo_tile_t * tile = &topo->tiles[ j ];
     848        1386 :       if( !strcmp( tile->name, *p ) ) {
     849          42 :         auto_tile_cpu( tile, cpus, &cpu_idx, cpu_assigned, cpu_ordering, skip_ht_pairs );
     850          42 :       }
     851        1386 :     }
     852          42 :   }
     853             : 
     854             :   /* Make sure all the tiles we haven't set are supposed to be floating. */
     855         204 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     856         198 :     fd_topo_tile_t * tile = &topo->tiles[ i ];
     857         198 :     if( tile->cpu_idx!=ULONG_MAX ) continue;
     858             : 
     859          24 :     int found = 0;
     860          81 :     for( char const ** p = FLOATING; *p; p++ ) {
     861          81 :       if( !strcmp( tile->name, *p ) ) {
     862          24 :         found = 1;
     863          24 :         break;
     864          24 :       }
     865          81 :     }
     866             : 
     867          24 :     if( FD_UNLIKELY( !found ) ) FD_LOG_WARNING(( "auto layout cannot affine tile `%s:%lu` because it is unknown. Leaving it floating", tile->name, tile->kind_id ));
     868          24 :   }
     869             : 
     870           6 :   topo->agave_affinity_cnt = 0UL;
     871           6 :   if( FD_UNLIKELY( reserve_agave_cores ) ) {
     872         645 :     for( ulong i=cpu_idx; i<cpus->cpu_cnt; i++ ) {
     873         642 :       if( FD_UNLIKELY( !cpus->cpu[ cpu_ordering[ i ] ].online ) ) continue;
     874         642 :       if( FD_UNLIKELY( cpu_bv_test( cpu_assigned, cpu_ordering[ i ] ) ) ) continue;
     875             : 
     876         636 :       if( FD_LIKELY( topo->agave_affinity_cnt<sizeof(topo->agave_affinity_cpu_idx)/sizeof(topo->agave_affinity_cpu_idx[0]) ) ) {
     877         636 :         topo->agave_affinity_cpu_idx[ topo->agave_affinity_cnt++ ] = cpu_ordering[ i ];
     878         636 :       }
     879         636 :     }
     880           3 :   }
     881           6 : }
     882             : 
     883             : void
     884             : fd_topob_auto_layout( fd_topo_t * topo,
     885           0 :                       int         reserve_agave_cores ) {
     886           0 :   fd_topo_cpus_t cpus[1];
     887           0 :   fd_topo_cpus_init( cpus );
     888           0 :   fd_topob_auto_layout_cpus( topo, cpus, reserve_agave_cores );
     889           0 : }
     890             : 
     891             : ulong
     892             : fd_numa_node_idx( ulong cpu_idx );
     893             : 
     894             : static void
     895          24 : initialize_numa_assignments( fd_topo_t * topo ) {
     896             :   /* Assign workspaces to NUMA nodes.  The heuristic here is pretty
     897             :      simple for now: workspaces go on the NUMA node of the first
     898             :      tile which maps the largest object in the workspace. */
     899             : 
     900          72 :   for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
     901          48 :     ulong max_footprint = 0UL;
     902          48 :     ulong max_obj = ULONG_MAX;
     903             : 
     904         192 :     for( ulong j=0UL; j<topo->obj_cnt; j++ ) {
     905         144 :       fd_topo_obj_t * obj = &topo->objs[ j ];
     906         144 :       if( obj->wksp_id!=i ) continue;
     907          72 :       if( FD_UNLIKELY( !obj->footprint ) ) FD_LOG_ERR(( "obj %lu (%s) has invalid parameters", j, obj->name ));
     908             : 
     909          72 :       if( FD_UNLIKELY( !max_footprint || obj->footprint>max_footprint ) ) {
     910          48 :         max_footprint = obj->footprint;
     911          48 :         max_obj = j;
     912          48 :       }
     913          72 :     }
     914             : 
     915          48 :     if( FD_UNLIKELY( max_obj==ULONG_MAX ) ) FD_LOG_ERR(( "no object found for workspace %s", topo->workspaces[ i ].name ));
     916             : 
     917          48 :     int found_strict   = 0;
     918          48 :     int found_lazy     = 0;
     919          48 :     int found_assigned = 0;
     920          72 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     921          48 :       fd_topo_tile_t * tile = &topo->tiles[ j ];
     922          48 :       if( FD_UNLIKELY( tile->tile_obj_id==max_obj && tile->cpu_idx<FD_TILE_MAX ) ) {
     923          24 :         topo->workspaces[ i ].numa_idx = fd_numa_node_idx( tile->cpu_idx );
     924          24 :         FD_TEST( topo->workspaces[ i ].numa_idx!=ULONG_MAX );
     925          24 :         found_strict   = 1;
     926          24 :         found_lazy     = 1;
     927          24 :         found_assigned = 1;
     928          24 :         break;
     929          24 :       } else if( FD_UNLIKELY( tile->tile_obj_id==max_obj && tile->cpu_idx>=FD_TILE_MAX ) ) {
     930           0 :         topo->workspaces[ i ].numa_idx = 0;
     931           0 :         found_lazy = 1;
     932           0 :         break;
     933           0 :       }
     934          48 :     }
     935             : 
     936          48 :     if( FD_LIKELY( !found_strict ) ) {
     937          24 :       for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     938          24 :         fd_topo_tile_t * tile = &topo->tiles[ j ];
     939          72 :         for( ulong k=0UL; k<tile->uses_obj_cnt; k++ ) {
     940          72 :           if( FD_LIKELY( tile->uses_obj_id[ k ]==max_obj && tile->cpu_idx<FD_TILE_MAX ) ) {
     941          24 :             topo->workspaces[ i ].numa_idx = fd_numa_node_idx( tile->cpu_idx );
     942          24 :             FD_TEST( topo->workspaces[ i ].numa_idx!=ULONG_MAX );
     943          24 :             found_lazy     = 1;
     944          24 :             found_assigned = 1;
     945          24 :             break;
     946          48 :           } else if( FD_UNLIKELY( tile->uses_obj_id[ k ]==max_obj ) && tile->cpu_idx>=FD_TILE_MAX ) {
     947           0 :             topo->workspaces[ i ].numa_idx = 0;
     948           0 :             found_lazy = 1;
     949             :             /* Don't break, keep looking -- a tile with a CPU assignment
     950             :                might also use object in which case we want to use that
     951             :                NUMA node. */
     952           0 :           }
     953          72 :         }
     954             : 
     955          24 :         if( FD_UNLIKELY( found_assigned ) ) break;
     956          24 :       }
     957          24 :     }
     958             : 
     959          48 :     if( FD_UNLIKELY( !found_lazy ) ) FD_LOG_ERR(( "no tile uses object %s for workspace %s", topo->objs[ max_obj ].name, topo->workspaces[ i ].name ));
     960          48 :   }
     961          24 : }
     962             : 
     963             : void
     964             : fd_topob_finish( fd_topo_t *                topo,
     965          24 :                  fd_topo_obj_callbacks_t ** callbacks ) {
     966          48 :   for( ulong z=0UL; z<topo->tile_cnt; z++ ) {
     967          24 :     fd_topo_tile_t * tile = &topo->tiles[ z ];
     968             : 
     969          24 :     ulong in_cnt = 0UL;
     970          24 :     for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     971           0 :       if( FD_UNLIKELY( !tile->in_link_poll[ i ] ) ) continue;
     972           0 :       in_cnt++;
     973           0 :     }
     974             : 
     975          24 :     FD_TEST( !fd_pod_replacef_ulong( topo->props, in_cnt, "obj.%lu.in_cnt", tile->metrics_obj_id ) );
     976          24 :   }
     977             : 
     978          72 :   for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
     979          48 :     fd_topo_wksp_t * wksp = &topo->workspaces[ i ];
     980             : 
     981          48 :     ulong loose_sz = 0UL;
     982         192 :     for( ulong j=0UL; j<topo->obj_cnt; j++ ) {
     983         144 :       fd_topo_obj_t * obj = &topo->objs[ j ];
     984         144 :       if( FD_UNLIKELY( obj->wksp_id!=wksp->id ) ) continue;
     985             : 
     986          72 :       fd_topo_obj_callbacks_t * cb = NULL;
     987         144 :       for( ulong i=0UL; callbacks[ i ]; i++ ) {
     988         144 :         if( FD_UNLIKELY( !strcmp( callbacks[ i ]->name, obj->name ) ) ) {
     989          72 :           cb = callbacks[ i ];
     990          72 :           break;
     991          72 :         }
     992         144 :       }
     993          72 :       if( FD_UNLIKELY( !cb ) ) FD_LOG_ERR(( "no callbacks for object %s", obj->name ));
     994             : 
     995          72 :       if( FD_UNLIKELY( cb->loose ) ) loose_sz += cb->loose( topo, obj );
     996          72 :     }
     997             : 
     998          48 :     ulong part_max = wksp->part_max;
     999          48 :     if( !part_max ) part_max = (loose_sz / (64UL << 10)); /* alloc + residual padding */
    1000          48 :     part_max += 3; /* for initial alignment */
    1001          48 :     ulong offset = fd_ulong_align_up( fd_wksp_private_data_off( part_max ), fd_topo_workspace_align() );
    1002             : 
    1003         192 :     for( ulong j=0UL; j<topo->obj_cnt; j++ ) {
    1004         144 :       fd_topo_obj_t * obj = &topo->objs[ j ];
    1005         144 :       if( FD_UNLIKELY( obj->wksp_id!=wksp->id ) ) continue;
    1006             : 
    1007          72 :       fd_topo_obj_callbacks_t * cb = NULL;
    1008         144 :       for( ulong i=0UL; callbacks[ i ]; i++ ) {
    1009         144 :         if( FD_UNLIKELY( !strcmp( callbacks[ i ]->name, obj->name ) ) ) {
    1010          72 :           cb = callbacks[ i ];
    1011          72 :           break;
    1012          72 :         }
    1013         144 :       }
    1014          72 :       if( FD_UNLIKELY( !cb ) ) FD_LOG_ERR(( "no callbacks for object %s", obj->name ));
    1015             : 
    1016          72 :       ulong align_ = cb->align( topo, obj );
    1017          72 :       if( FD_UNLIKELY( !fd_ulong_is_pow2( align_ ) ) ) FD_LOG_ERR(( "Return value of fdctl_obj_align(%s,%lu) is not a power of 2", obj->name, obj->id ));
    1018          72 :       offset = fd_ulong_align_up( offset, align_ );
    1019          72 :       obj->offset = offset;
    1020          72 :       obj->footprint = cb->footprint( topo, obj );
    1021          72 :       if( FD_UNLIKELY( 0!=strcmp( obj->name, "tile" ) && (!obj->footprint || obj->footprint>LONG_MAX) ) ) {
    1022           0 :         FD_LOG_ERR(( "fdctl_obj_footprint(%s,%lu) failed", obj->name, obj->id ));
    1023           0 :       }
    1024          72 :       offset += obj->footprint;
    1025          72 :     }
    1026             : 
    1027          48 :     ulong footprint = fd_ulong_align_up( offset, fd_topo_workspace_align() );
    1028             : 
    1029          48 :     part_max = fd_ulong_max( part_max, wksp->min_part_max );
    1030          48 :     loose_sz = fd_ulong_max( loose_sz, wksp->min_loose_sz );
    1031             : 
    1032             :     /* Compute footprint for a workspace that can store our footprint,
    1033             :        with an extra align of padding in case gaddr_lo is not aligned. */
    1034          48 :     ulong total_wksp_footprint = fd_wksp_footprint( part_max, footprint + fd_topo_workspace_align() + loose_sz );
    1035             : 
    1036          48 :     ulong page_sz = topo->max_page_size;
    1037          48 :     if( total_wksp_footprint < topo->gigantic_page_threshold ) page_sz = FD_SHMEM_HUGE_PAGE_SZ;
    1038          48 :     if( FD_UNLIKELY( page_sz!=FD_SHMEM_HUGE_PAGE_SZ && page_sz!=FD_SHMEM_GIGANTIC_PAGE_SZ ) ) FD_LOG_ERR(( "invalid page_sz" ));
    1039             : 
    1040          48 :     ulong wksp_aligned_footprint = fd_ulong_align_up( total_wksp_footprint, page_sz );
    1041             : 
    1042             :     /* Give any leftover space in the underlying shared memory to the
    1043             :        data region of the workspace, since we might as well use it. */
    1044          48 :     wksp->part_max = part_max;
    1045          48 :     wksp->known_footprint = footprint;
    1046          48 :     wksp->total_footprint = wksp_aligned_footprint - fd_ulong_align_up( fd_wksp_private_data_off( part_max ), fd_topo_workspace_align() );
    1047          48 :     wksp->page_sz = page_sz;
    1048          48 :     wksp->page_cnt = wksp_aligned_footprint / page_sz;
    1049          48 :   }
    1050             : 
    1051             :   /* Fingerprint the computed layout so attaching processes can detect
    1052             :      offset divergence from a different build or config. */
    1053          24 :   ulong layout_hash = 0UL;
    1054          96 :   for( ulong i=0UL; i<topo->obj_cnt; i++ ) {
    1055          72 :     fd_topo_obj_t const * obj = &topo->objs[ i ];
    1056          72 :     layout_hash = fd_hash( layout_hash, obj->name, strlen( obj->name ) );
    1057          72 :     layout_hash = fd_hash( layout_hash, topo->workspaces[ obj->wksp_id ].name, strlen( topo->workspaces[ obj->wksp_id ].name ) );
    1058          72 :     layout_hash = fd_hash( layout_hash, &obj->offset, sizeof(obj->offset) );
    1059          72 :     layout_hash = fd_hash( layout_hash, &obj->footprint, sizeof(obj->footprint) );
    1060          72 :   }
    1061          24 :   topo->layout_hash = layout_hash;
    1062             : 
    1063          24 :   initialize_numa_assignments( topo );
    1064             : 
    1065          24 :   fd_topob_validate_cpu_overlaps( topo );
    1066          24 :   validate( topo );
    1067          24 : }

Generated by: LCOV version 1.14