#include typedef unsigned int K; // workaround type for trac#185; used here as the ticket's examples use a spontaneous float typedef struct {} t_unused; struct request { unsigned int src_id; unsigned int tgt_id; DLISTED_MGD_EXPL_IN(request, ht_by_src) DLISTED_MGD_EXPL_IN(request, ht_by_tgt) }; DLISTED_MGD_EXPL_OUT(request, ht_by_src) DLISTED_MGD_EXPL_OUT(request, ht_by_tgt) size_t hash( unsigned int k ) { // not really a hash function, not really the point return k; } unsigned int key( request_in_ht_by_src & v ) { return v.src_id; } #include DATA_EXCEPTION(ht_fill_limit_crossed)( void * theHashtable; bool want_throwResume_ht_auto_resize_pending; size_t size_for_ht_auto_resize_pending; ); void ?{}(ht_fill_limit_crossed & this, void * theHashtable) { VTABLE_INIT(this, ht_fill_limit_crossed); this.theHashtable = theHashtable; this.want_throwResume_ht_auto_resize_pending = false; this.size_for_ht_auto_resize_pending = 0; } const char * ht_fill_limit_crossed_msg(ht_fill_limit_crossed * this) { return "ht_fill_limit_crossed"; } VTABLE_INSTANCE(ht_fill_limit_crossed)(ht_fill_limit_crossed_msg); DATA_EXCEPTION(ht_auto_resize_pending)( void * theHashtable; size_t new_size; ); void ?{}(ht_auto_resize_pending & this, void * theHashtable, size_t new_size) { VTABLE_INIT(this, ht_auto_resize_pending); this.theHashtable = theHashtable; this.new_size = new_size; } const char * ht_auto_resize_pending_msg(ht_auto_resize_pending * this) { return "ht_auto_resize_pending"; } VTABLE_INSTANCE(ht_auto_resize_pending)(ht_auto_resize_pending_msg); trait pretendsToMatter( dtype TTT ) { void actsmart(TTT &); }; forall( dtype TTTx ) void actsmart(TTTx &) {} // probable bug, wrt otype Tt_unused... // 1. changing to dtype Tt_unused crashes GenPoly // 2. declaring function check_ff_warning as concrete, i.e. operating on type hashtable_rbs(t_unused) makes cfa-cc generate bad C // in both cases, it's on the throwResume call // where it implicitly uses this.defaultResumptionHandler as a throwResume argument // whereas that, of course, has to be surrounded in a cast // at GenPoly breakpoint, type of said cast appears as CFA generic struct, even as the "this" parameter appears as C struct; casted type ... // 1. is hashtable_rbs(Tt_unused); assertion complains you don't need a type arg here // 2. shows up in -CFA output as hashtable_rbs(), which is bad C; expecting hashtable_rbs* forall( otype Tt_unused | pretendsToMatter(Tt_unused) ) { // hashtable of request by source struct hashtable_rbs { size_t n_buckets; dlist(request_in_ht_by_src, request) *buckets; size_t item_count; float ff_next_warn_up; float ff_warn_step_factor; void (*defaultResumptionHandler) (ht_fill_limit_crossed &); }; void ?{}( hashtable_rbs(Tt_unused) & this ) = void; } forall( otype Tt_unused | pretendsToMatter(Tt_unused) | { void defaultResumptionHandler(ht_fill_limit_crossed &); } ) { void ?{}( hashtable_rbs(Tt_unused) & this, size_t n_buckets, dlist(request_in_ht_by_src, request) *buckets, float ff_next_warn_up, float ff_warn_step_factor ) { printf( "base hashtable ctor with %ld buckets at %p\n", n_buckets, buckets); this.n_buckets = n_buckets; this.buckets = buckets; this.item_count = 0; this.ff_next_warn_up = ff_next_warn_up; this.ff_warn_step_factor = ff_warn_step_factor; this.defaultResumptionHandler = defaultResumptionHandler; for ( i; n_buckets ) { ?{}( this.buckets[i] ); } } void ?{}( hashtable_rbs(Tt_unused) & this, size_t n_buckets, dlist(request_in_ht_by_src, request) *buckets ) { printf( "base hashtable ctor with default warning steps\n" ); ( this ) { n_buckets, buckets, 0.5, 2 }; } } // this fwd declaration is artifact of workaround trac#192 void defaultResumptionHandler( ht_auto_resize_pending & ex ); forall( otype Tt_unused | pretendsToMatter(Tt_unused) ) { float fill_frac( hashtable_rbs(Tt_unused) & this ) with(this) { return ((float)item_count) / n_buckets; } size_t bucket_of( hashtable_rbs(Tt_unused) & this, K k ) { return hash(k) % this.n_buckets; } request & get( hashtable_rbs(Tt_unused) & this, K k ) with (this) { dlist(request_in_ht_by_src, request) & bucket = buckets[ bucket_of(this, k) ]; for ( request_in_ht_by_src * item = & $tempcv_e2n(bucket`first); item != 0p; item = & $tempcv_e2n((*item)`next) ) { if ( key(*item) == k ) { return *item; } } return *0p; } void check_ff_warning( hashtable_rbs(Tt_unused) & this ) with (this) { if (fill_frac(this) > ff_next_warn_up) { ht_fill_limit_crossed ex1 = { &this }; throwResume ex1; // workaround trac#192: want the second throwResume to be in __dynamic_defaultResumptionHandler // ... want base hashtable decoupled from resize if ( ex1.want_throwResume_ht_auto_resize_pending ) { throwResume( (ht_auto_resize_pending) { & this, ex1.size_for_ht_auto_resize_pending } ); } } } void put( hashtable_rbs(Tt_unused) & this, request & v ) with (this) { check_ff_warning(this); K k = key( $tempcv_e2n(v) ); dlist(request_in_ht_by_src, request) & bucket = buckets[ bucket_of(this, k) ]; for ( request_in_ht_by_src * item = & $tempcv_e2n(bucket`first); item != 0p; item = & $tempcv_e2n((*item)`next) ) { if ( key(*item) == k ) { remove(*item); break; } } insert_first(bucket, v); this.item_count ++; } } // tactical usage: // HASHTABLE_RBS_STATIC(n, ht) // // intended equivalent: // hashtable_rbs_static(Z(n)) ht; #define HASHTABLE_RBS_STATIC(n_buckets, obj) \ struct __hashtable_static_ ## obj { \ inline hashtable_rbs(t_unused); \ dlist(request_in_ht_by_src, request) $items[n_buckets]; \ }; \ void ?{}( __hashtable_static_ ## obj & this ) { \ ((hashtable_rbs(t_unused) &)this){ n_buckets, this.$items }; \ } \ __hashtable_static_ ## obj obj; void defaultResumptionHandler(ht_fill_limit_crossed & ex) { hashtable_rbs(t_unused) & ht = *(hashtable_rbs(t_unused) *)ex.theHashtable; printf("base default resumption handler ht_fill_limit_crossed with ht filled at %f\n", fill_frac(ht)); ht.ff_next_warn_up *= ht.ff_warn_step_factor; } void defaultTerminationHandler(ht_fill_limit_crossed &) = void; trait heaped(dtype T) { T * alloc( size_t ); void free( void * ); }; void __dynamic_defaultResumptionHandler(ht_fill_limit_crossed &); forall( otype Tt_unused ) { struct hashtable_rbs_dynamic { inline hashtable_rbs(Tt_unused); struct resize_policy { // When fill factor exceeds grow limit, grow big enough for // resulting fill factor to be lower than grow_target. Vice versa. // Using different grow and shrink limits prevents noisy current // size from triggering grow-shrink oscillation. OK to use same // grow and shrink targets. float grow_limit, shrink_limit, grow_target, shrink_target; // warn with exception but do nothing, this many -1 times, then actually resize unsigned short int warns_per_grow, warns_per_shrink; // Don't shrink below. size_t nbuckets_floor; } policy; dlist(request_in_ht_by_src, request) * (*alloc)( size_t ); void (*free)( void * ); }; } // will be in list api void splice_all_to_last( dlist(request_in_ht_by_src, request) & src_to_empty, dlist(request_in_ht_by_src, request) & snk_to_fill_at_last ) { // will re-implement as an actual splice while ( & src_to_empty`first != 0p ) { insert_last( snk_to_fill_at_last, pop_first( src_to_empty ) ); } } forall( otype Tt_unused | heaped( dlist(request_in_ht_by_src, request) ) ) { void ?{}( hashtable_rbs_dynamic(Tt_unused).resize_policy & this, size_t nbuckets_floor ) { printf("default dynamic policy ctor\n"); (this.grow_limit) {2.0}; (this.shrink_limit) {0.5}; (this.grow_target) {1.0}; (this.shrink_target) {1.0}; (this.warns_per_grow) {4}; (this.warns_per_shrink){4}; (this.nbuckets_floor) {nbuckets_floor}; } void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, size_t n_buckets, hashtable_rbs_dynamic(Tt_unused).resize_policy rp ) { printf("ctor hashtable_rbs_dynamic{ size_t, resize_policy }\n"); float first_first_warn_up = rp.grow_target; float ff_warn_step_factor = (rp.grow_limit / rp.grow_target) \ ( 1. / rp.warns_per_grow ); void (*defaultResumptionHandler) (ht_fill_limit_crossed &) = __dynamic_defaultResumptionHandler; dlist(request_in_ht_by_src, request) *buckets = alloc(n_buckets); ( ( hashtable_rbs( Tt_unused ) & ) this ){ n_buckets, buckets, first_first_warn_up, ff_warn_step_factor }; ( this.policy ){ rp }; this.alloc = alloc; this.free = free; } void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, hashtable_rbs_dynamic(Tt_unused).resize_policy rp ) { printf("ctor hashtable_rbs_dynamic{ resize_policy }\n"); ( this ) { rp.nbuckets_floor, rp }; } void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, size_t n_buckets ) { printf("ctor hashtable_rbs_dynamic{ size_t }\n"); ( this ) { n_buckets, (hashtable_rbs_dynamic(Tt_unused).resize_policy){ n_buckets } }; } void ^?{}( hashtable_rbs_dynamic(Tt_unused) & this ) { free(this.buckets); } void rehashToLarger( hashtable_rbs_dynamic(Tt_unused) & this, size_t new_n_buckets ) with(this) { printf("resizing from %ld to %ld, old buckets at %p\n", n_buckets, new_n_buckets, buckets); // collect hash items from old buckets dlist(request_in_ht_by_src, request) items; for (i; n_buckets) { splice_all_to_last( buckets[i], items ); } // make empty hash table of new size dlist(request_in_ht_by_src, request) *oldBuckets = buckets; float oldFfWarnStepFactor = ff_warn_step_factor; float newFfNextWarnUp = ((float)item_count) / ((float) new_n_buckets); ^?{}( (hashtable_rbs(Tt_unused) &)this ); free( oldBuckets ); ?{}( (hashtable_rbs(Tt_unused) &)this, new_n_buckets, alloc(new_n_buckets), newFfNextWarnUp, oldFfWarnStepFactor ); // fill new table with old items while ( & items`first != 0p ) { put( this, pop_first( items ) ); } } } forall( otype Tt_unused ) { void rehashToLarger_STEP( hashtable_rbs_dynamic(Tt_unused) & this, size_t new_n_buckets ) with (this) { rehashToLarger( this, new_n_buckets ); } } void defaultResumptionHandler( ht_auto_resize_pending & ex ) { hashtable_rbs_dynamic(t_unused) & ht = *(hashtable_rbs_dynamic(t_unused) *)ex.theHashtable; printf("auto-resize unhandled: proceeding with resize\n"); rehashToLarger_STEP( ht, ex.new_size ); } void __dynamic_defaultResumptionHandler(ht_fill_limit_crossed & ex) { hashtable_rbs_dynamic(t_unused) & ht = *(hashtable_rbs_dynamic(t_unused) *)ex.theHashtable; printf("dynamic warning received with fill_frac = %f and buckets at %p\n", fill_frac(ht), ht.buckets); if ( fill_frac( ht ) >= ht.policy.grow_limit ) { float grow_amount = ht.policy.grow_limit / ht.policy.grow_target; ex.want_throwResume_ht_auto_resize_pending = true; ex.size_for_ht_auto_resize_pending = ( size_t )( grow_amount * ht.n_buckets ); } else { // base handler, not specialized for dynamic defaultResumptionHandler( ex ); } } #include void basicFillingTestHelper( hashtable_rbs(t_unused) & ht, size_t n_elems ) { request & wasnt_found = get(ht, 17); assert( &wasnt_found == 0p ); request r; r.src_id = 117; r.tgt_id = 998; put(ht, r); request & found = get(ht, 117); assert( &found == &r ); & wasnt_found = & get(ht, 998); assert( &wasnt_found == 0p ); request rs[n_elems]; for (i; n_elems) { rs[i].src_id = 8000 * i; put(ht, rs[i]); } assert( & get(ht, 117 ) ); assert( & get(ht, 8000*25 ) ); assert(! & get(ht, 8000*25+1) ); } void basicFillingTest_static() { printf("---start basic fill test static ----\n"); HASHTABLE_RBS_STATIC(67, ht) basicFillingTestHelper(ht, 500); } void basicFillingTest_dynamic() { dlist(request_in_ht_by_src, request) * (*old_alloc)( size_t ) = alloc; dlist(request_in_ht_by_src, request) * alloc( size_t n ) { dlist(request_in_ht_by_src, request) * ret = old_alloc(n); printf("alloc'ed at %p\n", ret); return ret; } void (*old_free)( void * ) = free; void free( void * o ) { printf("free'ing at %p\n", o); old_free(o); } printf("---start basic fill test dynamic ----\n"); hashtable_rbs_dynamic(t_unused) ht = { 113 }; basicFillingTestHelper(ht, 500); } // Demonstrates user-provided instrumentation monitoring a fixed-size hash table void logTest() { printf("---start log test ----\n"); HASHTABLE_RBS_STATIC(67, ht) try { basicFillingTestHelper(ht, 500); } catchResume( ht_fill_limit_crossed * ) { printf("log test instrumentation runs\n"); throwResume; } } // Demonstrates "snoozing" a growing hash table's auto-resize event, // in that that next call to put will get the resize exception instead. void snoozeTest() { printf("---start snooze test ----\n"); hashtable_rbs_dynamic(t_unused) ht = { 113 }; bool lastResizeSnoozed = false; try { basicFillingTestHelper(ht, 500); } catchResume( ht_auto_resize_pending * ) { if ( lastResizeSnoozed == false ) { lastResizeSnoozed = true; printf("snooze test intervention decides to snooze this time\n"); } else { lastResizeSnoozed = false; printf("snooze test intervention decides to allow the resize\n"); throwResume; } } } int main() { basicFillingTest_static(); basicFillingTest_dynamic(); logTest(); snoozeTest(); }