diff --git a/ext/-test-/st/numhash/numhash.c b/ext/-test-/st/numhash/numhash.c index 599678dde1386c..6fe079312284a8 100644 --- a/ext/-test-/st/numhash/numhash.c +++ b/ext/-test-/st/numhash/numhash.c @@ -42,12 +42,10 @@ numhash_aset(VALUE self, VALUE key, VALUE data) } static int -numhash_i(st_data_t key, st_data_t value, st_data_t arg, int error) +numhash_i(st_data_t key, st_data_t value, st_data_t arg) { VALUE ret; - if (key == 0 && value == 0 && error == 1) rb_raise(rb_eRuntimeError, "numhash modified"); ret = rb_yield_values(3, (VALUE)key, (VALUE)value, (VALUE)arg); - if (ret == Qtrue) return ST_CHECK; return ST_CONTINUE; } @@ -55,8 +53,11 @@ static VALUE numhash_each(VALUE self) { st_table *table = DATA_PTR(self); - st_data_t data = (st_data_t)self; - return st_foreach_check(table, numhash_i, data, data) ? Qtrue : Qfalse; + int res; + table->safe_mode = ST_SAFEMODE; + res = st_foreach_check(table, numhash_i, (st_data_t)self); + table->safe_mode = ST_REGULAR; + return res ? Qtrue : Qfalse; } static int @@ -99,7 +100,7 @@ static VALUE numhash_delete_safe(VALUE self, VALUE key) { st_data_t val, k = (st_data_t)key; - if (st_delete_safe((st_table *)DATA_PTR(self), &k, &val, (st_data_t)self)) { + if (st_delete((st_table *)DATA_PTR(self), &k, &val)) { return val; } return Qnil; diff --git a/ext/tk/tkutil/tkutil.c b/ext/tk/tkutil/tkutil.c index 956c6737c9e7e6..7cad1eee0c4e70 100644 --- a/ext/tk/tkutil/tkutil.c +++ b/ext/tk/tkutil/tkutil.c @@ -267,7 +267,7 @@ to_strkey(key, value, hash) VALUE hash; { rb_hash_aset(hash, rb_funcall(key, ID_to_s, 0, 0), value); - return ST_CHECK; + return ST_CONTINUE; } static VALUE @@ -279,7 +279,7 @@ tk_symbolkey2str(self, keys) if NIL_P(keys) return new_keys; keys = rb_convert_type(keys, T_HASH, "Hash", "to_hash"); - st_foreach_check(RHASH_TBL(keys), to_strkey, new_keys, Qundef); + st_foreach_check(RHASH_TBL(keys), to_strkey, new_keys); return new_keys; } @@ -658,11 +658,11 @@ push_kv(key, val, args) #endif rb_ary_push(ary, key2keyname(key)); - if (val == TK_None) return ST_CHECK; + if (val == TK_None) return ST_CONTINUE; rb_ary_push(ary, get_eval_string_core(val, Qnil, RARRAY_PTR(args)[1])); - return ST_CHECK; + return ST_CONTINUE; } static VALUE @@ -674,7 +674,7 @@ hash2kv(hash, ary, self) volatile VALUE dst = rb_ary_new2(2 * RHASH_SIZE(hash)); volatile VALUE args = rb_ary_new3(2, dst, self); - st_foreach_check(RHASH_TBL(hash), push_kv, args, Qundef); + st_foreach_check(RHASH_TBL(hash), push_kv, args); if (NIL_P(ary)) { return dst; @@ -702,11 +702,11 @@ push_kv_enc(key, val, args) #endif rb_ary_push(ary, key2keyname(key)); - if (val == TK_None) return ST_CHECK; + if (val == TK_None) return ST_CONTINUE; rb_ary_push(ary, get_eval_string_core(val, Qtrue, RARRAY_PTR(args)[1])); - return ST_CHECK; + return ST_CONTINUE; } static VALUE @@ -718,7 +718,7 @@ hash2kv_enc(hash, ary, self) volatile VALUE dst = rb_ary_new2(2 * RHASH_SIZE(hash)); volatile VALUE args = rb_ary_new3(2, dst, self); - st_foreach_check(RHASH_TBL(hash), push_kv_enc, args, Qundef); + st_foreach_check(RHASH_TBL(hash), push_kv_enc, args); if (NIL_P(ary)) { return dst; diff --git a/hash.c b/hash.c index e9937ffe717ba5..995cff59820414 100644 --- a/hash.c +++ b/hash.c @@ -23,7 +23,6 @@ static VALUE rb_hash_s_try_convert(VALUE, VALUE); -#define HASH_DELETED FL_USER1 #define HASH_PROC_DEFAULT FL_USER2 VALUE @@ -111,27 +110,10 @@ struct foreach_safe_arg { st_data_t arg; }; -static int -foreach_safe_i(st_data_t key, st_data_t value, struct foreach_safe_arg *arg) -{ - int status; - - status = (*arg->func)(key, value, arg->arg); - if (status == ST_CONTINUE) { - return ST_CHECK; - } - return status; -} - void st_foreach_safe(st_table *table, int (*func)(ANYARGS), st_data_t a) { - struct foreach_safe_arg arg; - - arg.tbl = table; - arg.func = (st_foreach_func *)func; - arg.arg = a; - if (st_foreach_check(table, foreach_safe_i, (st_data_t)&arg, Qundef)) { + if (st_foreach_check(table, func, a)) { rb_raise(rb_eRuntimeError, "hash modified during iteration"); } } @@ -145,28 +127,18 @@ struct hash_foreach_arg { }; static int -hash_foreach_iter(st_data_t key, st_data_t value, st_data_t argp, int err) +hash_foreach_iter(st_data_t key, st_data_t value, st_data_t argp) { struct hash_foreach_arg *arg = (struct hash_foreach_arg *)argp; int status; st_table *tbl; tbl = RHASH(arg->hash)->ntbl; - if ((VALUE)key == Qundef) return ST_CONTINUE; status = (*arg->func)((VALUE)key, (VALUE)value, arg->arg); if (RHASH(arg->hash)->ntbl != tbl) { rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); } - switch (status) { - case ST_DELETE: - st_delete_safe(tbl, &key, 0, Qundef); - FL_SET(arg->hash, HASH_DELETED); - case ST_CONTINUE: - break; - case ST_STOP: - return ST_STOP; - } - return ST_CHECK; + return status; } static VALUE @@ -175,9 +147,11 @@ hash_foreach_ensure(VALUE hash) RHASH(hash)->iter_lev--; if (RHASH(hash)->iter_lev == 0) { - if (FL_TEST(hash, HASH_DELETED)) { - st_cleanup_safe(RHASH(hash)->ntbl, Qundef); - FL_UNSET(hash, HASH_DELETED); + if (RHASH(hash)->ntbl->safe_mode == ST_HAS_DELETED) { + st_cleanup_safe(RHASH(hash)->ntbl); + } + else { + RHASH(hash)->ntbl->safe_mode = ST_REGULAR; } } return 0; @@ -186,7 +160,7 @@ hash_foreach_ensure(VALUE hash) static VALUE hash_foreach_call(struct hash_foreach_arg *arg) { - if (st_foreach_check(RHASH(arg->hash)->ntbl, hash_foreach_iter, (st_data_t)arg, Qundef)) { + if (st_foreach_check(RHASH(arg->hash)->ntbl, hash_foreach_iter, (st_data_t)arg)) { rb_raise(rb_eRuntimeError, "hash modified during iteration"); } return Qnil; @@ -200,6 +174,7 @@ rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) if (!RHASH(hash)->ntbl) return; RHASH(hash)->iter_lev++; + RHASH(hash)->ntbl->safe_mode = ST_SAFEMODE; arg.hash = hash; arg.func = (rb_foreach_func *)func; arg.arg = farg; @@ -437,7 +412,7 @@ rb_hash_rehash_i(VALUE key, VALUE value, VALUE arg) { st_table *tbl = (st_table *)arg; - if (key != Qundef) st_insert(tbl, key, value); + st_insert(tbl, key, value); return ST_CONTINUE; } @@ -649,7 +624,7 @@ rb_hash_default(int argc, VALUE *argv, VALUE hash) static VALUE rb_hash_set_default(VALUE hash, VALUE ifnone) { - rb_hash_modify(hash); + rb_hash_modify_check(hash); RHASH_IFNONE(hash) = ifnone; FL_UNSET(hash, HASH_PROC_DEFAULT); return ifnone; @@ -697,7 +672,7 @@ rb_hash_set_default_proc(VALUE hash, VALUE proc) { VALUE b; - rb_hash_modify(hash); + rb_hash_modify_check(hash); b = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc"); if (NIL_P(b) || !rb_obj_is_proc(b)) { rb_raise(rb_eTypeError, @@ -765,13 +740,7 @@ rb_hash_delete_key(VALUE hash, VALUE key) if (!RHASH(hash)->ntbl) return Qundef; - if (RHASH(hash)->iter_lev > 0) { - if (st_delete_safe(RHASH(hash)->ntbl, &ktmp, &val, Qundef)) { - FL_SET(hash, HASH_DELETED); - return (VALUE)val; - } - } - else if (st_delete(RHASH(hash)->ntbl, &ktmp, &val)) + if (st_delete(RHASH(hash)->ntbl, &ktmp, &val)) return (VALUE)val; return Qundef; } @@ -799,7 +768,7 @@ rb_hash_delete(VALUE hash, VALUE key) { VALUE val; - rb_hash_modify(hash); + rb_hash_modify_check(hash); val = rb_hash_delete_key(hash, key); if (val != Qundef) return val; if (rb_block_given_p()) { @@ -808,34 +777,6 @@ rb_hash_delete(VALUE hash, VALUE key) return Qnil; } -struct shift_var { - VALUE key; - VALUE val; -}; - -static int -shift_i(VALUE key, VALUE value, VALUE arg) -{ - struct shift_var *var = (struct shift_var *)arg; - - if (key == Qundef) return ST_CONTINUE; - if (var->key != Qundef) return ST_STOP; - var->key = key; - var->val = value; - return ST_DELETE; -} - -static int -shift_i_safe(VALUE key, VALUE value, VALUE arg) -{ - struct shift_var *var = (struct shift_var *)arg; - - if (key == Qundef) return ST_CONTINUE; - var->key = key; - var->val = value; - return ST_STOP; -} - /* * call-seq: * hsh.shift -> anArray or obj @@ -852,20 +793,14 @@ shift_i_safe(VALUE key, VALUE value, VALUE arg) static VALUE rb_hash_shift(VALUE hash) { - struct shift_var var; + st_data_t key = 0, val = 0; - rb_hash_modify(hash); - var.key = Qundef; - rb_hash_foreach(hash, RHASH(hash)->iter_lev > 0 ? shift_i_safe : shift_i, - (VALUE)&var); + rb_hash_modify_check(hash); - if (var.key != Qundef) { - if (RHASH(hash)->iter_lev > 0) { - rb_hash_delete_key(hash, var.key); - } - return rb_assoc_new(var.key, var.val); + if (RHASH(hash)->ntbl && st_shift(RHASH(hash)->ntbl, &key, &val)) { + return rb_assoc_new(key, val); } - else if (FL_TEST(hash, HASH_PROC_DEFAULT)) { + if (FL_TEST(hash, HASH_PROC_DEFAULT)) { return rb_funcall(RHASH_IFNONE(hash), id_yield, 2, hash, Qnil); } else { @@ -876,9 +811,8 @@ rb_hash_shift(VALUE hash) static int delete_if_i(VALUE key, VALUE value, VALUE hash) { - if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_yield_values(2, key, value))) { - rb_hash_delete_key(hash, key); + return ST_DELETE; } return ST_CONTINUE; } @@ -902,8 +836,9 @@ VALUE rb_hash_delete_if(VALUE hash) { RETURN_ENUMERATOR(hash, 0, 0); - rb_hash_modify(hash); - rb_hash_foreach(hash, delete_if_i, hash); + rb_hash_modify_check(hash); + if (RHASH(hash)->ntbl) + rb_hash_foreach(hash, delete_if_i, hash); return hash; } @@ -974,7 +909,6 @@ rb_hash_values_at(int argc, VALUE *argv, VALUE hash) static int select_i(VALUE key, VALUE value, VALUE result) { - if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_yield_values(2, key, value))) rb_hash_aset(result, key, value); return ST_CONTINUE; @@ -1008,7 +942,6 @@ rb_hash_select(VALUE hash) static int keep_if_i(VALUE key, VALUE value, VALUE hash) { - if (key == Qundef) return ST_CONTINUE; if (!RTEST(rb_yield_values(2, key, value))) { return ST_DELETE; } @@ -1030,7 +963,7 @@ rb_hash_select_bang(VALUE hash) st_index_t n; RETURN_ENUMERATOR(hash, 0, 0); - rb_hash_modify(hash); + rb_hash_modify_check(hash); if (!RHASH(hash)->ntbl) return Qnil; n = RHASH(hash)->ntbl->num_entries; @@ -1055,17 +988,12 @@ VALUE rb_hash_keep_if(VALUE hash) { RETURN_ENUMERATOR(hash, 0, 0); - rb_hash_modify(hash); - rb_hash_foreach(hash, keep_if_i, hash); + rb_hash_modify_check(hash); + if (RHASH(hash)->ntbl) + rb_hash_foreach(hash, keep_if_i, hash); return hash; } -static int -clear_i(VALUE key, VALUE value, VALUE dummy) -{ - return ST_DELETE; -} - /* * call-seq: * hsh.clear -> hsh @@ -1084,10 +1012,7 @@ rb_hash_clear(VALUE hash) if (!RHASH(hash)->ntbl) return hash; if (RHASH(hash)->ntbl->num_entries > 0) { - if (RHASH(hash)->iter_lev > 0) - rb_hash_foreach(hash, clear_i, 0); - else - st_clear(RHASH(hash)->ntbl); + st_clear(RHASH(hash)->ntbl); } return hash; @@ -1134,9 +1059,7 @@ rb_hash_aset(VALUE hash, VALUE key, VALUE val) static int replace_i(VALUE key, VALUE val, VALUE hash) { - if (key != Qundef) { - rb_hash_aset(hash, key, val); - } + rb_hash_aset(hash, key, val); return ST_CONTINUE; } @@ -1217,7 +1140,6 @@ rb_hash_empty_p(VALUE hash) static int each_value_i(VALUE key, VALUE value) { - if (key == Qundef) return ST_CONTINUE; rb_yield(value); return ST_CONTINUE; } @@ -1252,7 +1174,6 @@ rb_hash_each_value(VALUE hash) static int each_key_i(VALUE key, VALUE value) { - if (key == Qundef) return ST_CONTINUE; rb_yield(key); return ST_CONTINUE; } @@ -1286,7 +1207,6 @@ rb_hash_each_key(VALUE hash) static int each_pair_i(VALUE key, VALUE value) { - if (key == Qundef) return ST_CONTINUE; rb_yield(rb_assoc_new(key, value)); return ST_CONTINUE; } @@ -1324,7 +1244,6 @@ rb_hash_each_pair(VALUE hash) static int to_a_i(VALUE key, VALUE value, VALUE ary) { - if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, rb_assoc_new(key, value)); return ST_CONTINUE; } @@ -1357,7 +1276,6 @@ inspect_i(VALUE key, VALUE value, VALUE str) { VALUE str2; - if (key == Qundef) return ST_CONTINUE; str2 = rb_inspect(key); if (RSTRING_LEN(str) > 1) { rb_str_cat2(str, ", "); @@ -1424,7 +1342,6 @@ rb_hash_to_hash(VALUE hash) static int keys_i(VALUE key, VALUE value, VALUE ary) { - if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, key); return ST_CONTINUE; } @@ -1455,7 +1372,6 @@ rb_hash_keys(VALUE hash) static int values_i(VALUE key, VALUE value, VALUE ary) { - if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, value); return ST_CONTINUE; } @@ -1514,7 +1430,6 @@ rb_hash_search_value(VALUE key, VALUE value, VALUE arg) { VALUE *data = (VALUE *)arg; - if (key == Qundef) return ST_CONTINUE; if (rb_equal(value, data[1])) { data[0] = Qtrue; return ST_STOP; @@ -1558,7 +1473,6 @@ eql_i(VALUE key, VALUE val1, VALUE arg) struct equal_data *data = (struct equal_data *)arg; st_data_t val2; - if (key == Qundef) return ST_CONTINUE; if (!st_lookup(data->tbl, key, &val2)) { data->result = Qfalse; return ST_STOP; @@ -1660,7 +1574,6 @@ hash_i(VALUE key, VALUE val, VALUE arg) st_index_t *hval = (st_index_t *)arg; st_index_t hdata[2]; - if (key == Qundef) return ST_CONTINUE; hdata[0] = rb_hash(key); hdata[1] = rb_hash(val); *hval ^= st_hash(hdata, sizeof(hdata), 0); @@ -1701,7 +1614,6 @@ rb_hash_hash(VALUE hash) static int rb_hash_invert_i(VALUE key, VALUE value, VALUE hash) { - if (key == Qundef) return ST_CONTINUE; rb_hash_aset(hash, value, key); return ST_CONTINUE; } @@ -1730,7 +1642,6 @@ rb_hash_invert(VALUE hash) static int rb_hash_update_i(VALUE key, VALUE value, VALUE hash) { - if (key == Qundef) return ST_CONTINUE; hash_update(hash, key); st_insert(RHASH(hash)->ntbl, key, value); return ST_CONTINUE; @@ -1739,7 +1650,6 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash) static int rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) { - if (key == Qundef) return ST_CONTINUE; if (rb_hash_has_key(hash, key)) { value = rb_yield_values(3, key, rb_hash_aref(hash, key), value); } @@ -1796,7 +1706,6 @@ rb_hash_update_func_i(VALUE key, VALUE value, VALUE arg0) struct update_arg *arg = (struct update_arg *)arg0; VALUE hash = arg->hash; - if (key == Qundef) return ST_CONTINUE; if (rb_hash_has_key(hash, key)) { value = (*arg->func)(key, rb_hash_aref(hash, key), value); } @@ -1853,7 +1762,6 @@ assoc_i(VALUE key, VALUE val, VALUE arg) { VALUE *args = (VALUE *)arg; - if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_equal(args[0], key))) { args[1] = rb_assoc_new(key, val); return ST_STOP; @@ -1891,7 +1799,6 @@ rassoc_i(VALUE key, VALUE val, VALUE arg) { VALUE *args = (VALUE *)arg; - if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_equal(args[0], val))) { args[1] = rb_assoc_new(key, val); return ST_STOP; @@ -3088,11 +2995,9 @@ env_invert(void) static int env_replace_i(VALUE key, VALUE val, VALUE keys) { - if (key != Qundef) { - env_aset(Qnil, key, val); - if (rb_ary_includes(keys, key)) { - rb_ary_delete(keys, key); - } + env_aset(Qnil, key, val); + if (rb_ary_includes(keys, key)) { + rb_ary_delete(keys, key); } return ST_CONTINUE; } @@ -3124,12 +3029,10 @@ env_replace(VALUE env, VALUE hash) static int env_update_i(VALUE key, VALUE val) { - if (key != Qundef) { - if (rb_block_given_p()) { - val = rb_yield_values(3, key, rb_f_getenv(Qnil, key), val); - } - env_aset(Qnil, key, val); + if (rb_block_given_p()) { + val = rb_yield_values(3, key, rb_f_getenv(Qnil, key), val); } + env_aset(Qnil, key, val); return ST_CONTINUE; } diff --git a/include/ruby/st.h b/include/ruby/st.h index c0d5e01b1a613f..4576a2b26d4267 100644 --- a/include/ruby/st.h +++ b/include/ruby/st.h @@ -74,9 +74,18 @@ struct st_hash_type { #define ST_INDEX_BITS (sizeof(st_index_t) * CHAR_BIT) +typedef struct st_packed_entry { + st_index_t hash; + st_data_t key, val; +} st_packed_entry; + struct st_table { const struct st_hash_type *type; - st_index_t num_bins; + unsigned int safe_mode : 2; +#ifdef __GNUC__ + __extension__ +#endif + st_index_t num_bins : ST_INDEX_BITS - 2; unsigned int entries_packed : 1; #ifdef __GNUC__ /* @@ -100,12 +109,14 @@ struct st_table { struct st_packed_entry *entries; st_index_t real_entries; } packed; + struct st_packed_entry upacked; } as; }; #define st_is_member(table,key) st_lookup((table),(key),(st_data_t *)0) -enum st_retval {ST_CONTINUE, ST_STOP, ST_DELETE, ST_CHECK}; +enum st_retval {ST_CONTINUE, ST_STOP, ST_DELETE}; +enum st_safe_mode {ST_REGULAR = 0, ST_SAFEMODE, ST_HAS_DELETED = 3}; st_table *st_init_table(const struct st_hash_type *); st_table *st_init_table_with_size(const struct st_hash_type *, st_index_t); @@ -116,18 +127,18 @@ st_table *st_init_strtable_with_size(st_index_t); st_table *st_init_strcasetable(void); st_table *st_init_strcasetable_with_size(st_index_t); int st_delete(st_table *, st_data_t *, st_data_t *); /* returns 0:notfound 1:deleted */ -int st_delete_safe(st_table *, st_data_t *, st_data_t *, st_data_t); +int st_shift(st_table *, st_data_t *, st_data_t *); /* returns 0:noentries 1:shifted */ int st_insert(st_table *, st_data_t, st_data_t); int st_insert2(st_table *, st_data_t, st_data_t, st_data_t (*)(st_data_t)); int st_lookup(st_table *, st_data_t, st_data_t *); int st_get_key(st_table *, st_data_t, st_data_t *); int st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t *value, st_data_t arg), st_data_t arg); int st_foreach(st_table *, int (*)(ANYARGS), st_data_t); -int st_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t); +int st_foreach_check(st_table *, int (*)(ANYARGS), st_data_t); int st_reverse_foreach(st_table *, int (*)(ANYARGS), st_data_t); void st_add_direct(st_table *, st_data_t, st_data_t); void st_free_table(st_table *); -void st_cleanup_safe(st_table *, st_data_t); +void st_cleanup_safe(st_table *); void st_clear(st_table *); st_table *st_copy(st_table *); int st_numcmp(st_data_t, st_data_t); diff --git a/st.c b/st.c index ad943e925df2c3..65141c1e996d4d 100644 --- a/st.c +++ b/st.c @@ -25,11 +25,6 @@ struct st_table_entry { st_table_entry *fore, *back; }; -typedef struct st_packed_entry { - st_index_t hash; - st_data_t key, val; -} st_packed_entry; - #define STATIC_ASSERT(name, expr) typedef int static_assert_##name##_check[(expr) ? 1 : -1]; #define ST_DEFAULT_MAX_DENSITY 5 @@ -84,8 +79,16 @@ static void rehash(st_table *); #define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0) -#define do_hash(key,table) (st_index_t)(*(table)->type->hash)((key)) -#define do_hash_bin(key,table) (do_hash((key), (table))%(table)->num_bins) +#define HASH_MASK ((~(st_index_t)0) >> 1) +#define DELETED_BIT (~HASH_MASK) +#define DELETED(entry) ((entry)->hash & DELETED_BIT) +#define MARK_DELETED(table, entry) do { \ + (table)->safe_mode = ST_HAS_DELETED; \ + (table)->num_entries--; \ + (entry)->hash |= DELETED_BIT; \ +} while(0) + +#define do_hash(key,table) ((st_index_t)(*(table)->type->hash)((key)) & HASH_MASK) /* preparation for possible allocation improvements */ #define st_alloc_entry() (st_table_entry *)malloc(sizeof(st_table_entry)) @@ -117,6 +120,8 @@ st_realloc_bins(st_table_entry **bins, st_index_t newsize, st_index_t oldsize) #define PKEY_SET(table, i, v) (PKEY((table), (i)) = (v)) #define PVAL_SET(table, i, v) (PVAL((table), (i)) = (v)) #define PHASH_SET(table, i, v) (PHASH((table), (i)) = (v)) +#define PDELETED(table, i) DELETED(&PACKED_ENT((table), (i))) +#define PMARK_DELETED(table, i) MARK_DELETED((table), &PACKED_ENT((table), (i))) /* this function depends much on packed layout, so that it placed here */ static inline void @@ -130,6 +135,25 @@ remove_packed_entry(st_table *table, st_index_t i) } } +/* ultrapacking */ +#define MAX_UPACKED_HASH 1 +#define ureal_entries num_bins +//#define ULTRA_PACKED(table) ((table)->num_bins & ((~(st_index_t)0)<<1) == 0) +#define ULTRA_PACKED(table) ((table)->num_bins <= 1) +#define UPACKED_ENT(table) ((table)->as.upacked) +#define UPKEY(table) UPACKED_ENT(table).key +#define UPVAL(table) UPACKED_ENT(table).val +#define UPHASH(table) UPACKED_ENT(table).hash +#define UPKEY_SET(table, v) (UPKEY(table) = (v)) +#define UPVAL_SET(table, v) (UPVAL(table) = (v)) +#define UPHASH_SET(table, v) (UPHASH(table) = (v)) +#define UPDELETED(table) DELETED(&UPACKED_ENT(table)) +#define UPMARK_DELETED(table) MARK_DELETED((table), &UPACKED_ENT(table)) +#define remove_upacked_entry(table) do { \ + (table)->num_entries = 0; \ + (table)->num_bins = 0; \ +} while(0) + /* * MINSIZE is the minimum size of a dictionary. */ @@ -167,9 +191,9 @@ static const unsigned int primes[] = { 134217728 + 29, 268435456 + 3, 536870912 + 11, - 1073741824 + 85, 0 }; +#define PRIME_30BIT 1073741789 static st_index_t new_size(st_index_t size) @@ -187,6 +211,7 @@ new_size(st_index_t size) for (i = 0, newsize = MINSIZE; i < numberof(primes); i++, newsize <<= 1) { if (newsize > size) return primes[i]; } + if (newsize <= PRIME_30BIT) return PRIME_30BIT; /* Ran out of polynomials */ #ifndef NOT_RUBY rb_raise(rb_eRuntimeError, "st_table too big"); @@ -240,13 +265,14 @@ st_init_table_with_size(const struct st_hash_type *type, st_index_t size) tbl->num_entries = 0; tbl->entries_packed = size <= MAX_PACKED_HASH; if (tbl->entries_packed) { - size = ST_DEFAULT_PACKED_TABLE_SIZE; + size = size <= MAX_UPACKED_HASH ? 0 : ST_DEFAULT_PACKED_TABLE_SIZE; } else { size = new_size(size); /* round up to prime number */ } tbl->num_bins = size; - tbl->bins = st_alloc_bins(size); + tbl->safe_mode = 0; + tbl->bins = size ? st_alloc_bins(size) : 0; tbl->head = 0; tbl->tail = 0; @@ -295,12 +321,29 @@ st_init_strcasetable_with_size(st_index_t size) return st_init_table_with_size(&type_strcasehash, size); } +static int +clear_i(VALUE key, VALUE value, VALUE dummy) +{ + return ST_DELETE; +} + void st_clear(st_table *table) { register st_table_entry *ptr, *next; st_index_t i; + if (table->safe_mode) { + st_foreach_check(table, clear_i, 0); + return; + } + + if (ULTRA_PACKED(table)) { + table->num_entries = 0; + table->ureal_entries = 0; + return; + } + if (table->entries_packed) { table->num_entries = 0; table->real_entries = 0; @@ -324,15 +367,20 @@ st_clear(st_table *table) void st_free_table(st_table *table) { + table->safe_mode = 0; st_clear(table); - st_free_bins(table->bins, table->num_bins); + if (!ULTRA_PACKED(table)) + st_free_bins(table->bins, table->num_bins); st_dealloc_table(table); } size_t st_memsize(const st_table *table) { - if (table->entries_packed) { + if (ULTRA_PACKED(table)) { + return sizeof(table); + } + else if (table->entries_packed) { return table->num_bins * sizeof (void *) + sizeof(st_table); } else { @@ -394,8 +442,23 @@ find_packed_index(st_table *table, st_index_t hash_val, st_data_t key) return i; } +static inline int +check_upacked(st_table *table, st_index_t hash_val, st_data_t key) +{ + return table->ureal_entries && UPHASH(table) == hash_val && + EQUAL(table, key, UPKEY(table)); +} + #define collision_check 0 +#define RETURN(cond, value_ptr, value) do { \ + if (cond) { \ + if ((value_ptr) != 0) *(value_ptr) = (value); \ + return 1; \ + } \ + return 0; \ +} while(0) + int st_lookup(st_table *table, register st_data_t key, st_data_t *value) { @@ -404,24 +467,17 @@ st_lookup(st_table *table, register st_data_t key, st_data_t *value) hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + RETURN(check_upacked(table, hash_val, key), value, UPVAL(table)); + } + if (table->entries_packed) { st_index_t i = find_packed_index(table, hash_val, key); - if (i < table->real_entries) { - if (value != 0) *value = PVAL(table, i); - return 1; - } - return 0; + RETURN(i < table->real_entries, value, PVAL(table, i)); } ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); - - if (ptr == 0) { - return 0; - } - else { - if (value != 0) *value = ptr->record; - return 1; - } + RETURN(ptr != 0, value, ptr->record); } int @@ -432,24 +488,17 @@ st_get_key(st_table *table, register st_data_t key, st_data_t *result) hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + RETURN(check_upacked(table, hash_val, key), result, UPKEY(table)); + } + if (table->entries_packed) { st_index_t i = find_packed_index(table, hash_val, key); - if (i < table->real_entries) { - if (result != 0) *result = PKEY(table, i); - return 1; - } - return 0; + RETURN(i < table->real_entries, result, PKEY(table, i)); } ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); - - if (ptr == 0) { - return 0; - } - else { - if (result != 0) *result = ptr->key; - return 1; - } + RETURN(ptr != 0, result, ptr->key); } #undef collision_check @@ -545,6 +594,28 @@ add_packed_direct(st_table *table, st_data_t key, st_data_t value, st_index_t ha } } +static void +add_upacked_direct(st_table *table, st_data_t key, st_data_t value, st_index_t hash_val) +{ + if (table->ureal_entries) { + st_packed_entry ent = UPACKED_ENT(table); + st_packed_entry *entries = (st_packed_entry*)st_alloc_bins(ST_DEFAULT_PACKED_TABLE_SIZE); + table->num_bins = ST_DEFAULT_PACKED_TABLE_SIZE; + table->as.packed.entries = entries; + table->num_entries++; + table->real_entries = 2; + PACKED_ENT(table, 0) = ent; + PHASH_SET(table, 1, hash_val); + PKEY_SET(table, 1, key); + PVAL_SET(table, 1, value); + } + else { + UPHASH_SET(table, hash_val); + UPKEY_SET(table, key); + UPVAL_SET(table, value); + table->num_entries = table->ureal_entries = 1; + } +} int st_insert(register st_table *table, register st_data_t key, st_data_t value) @@ -555,6 +626,15 @@ st_insert(register st_table *table, register st_data_t key, st_data_t value) hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + if (check_upacked(table, hash_val, key)) { + UPVAL_SET(table, value); + return 1; + } + add_upacked_direct(table, key, value, hash_val); + return 0; + } + if (table->entries_packed) { st_index_t i = find_packed_index(table, hash_val, key); if (i < table->real_entries) { @@ -587,6 +667,16 @@ st_insert2(register st_table *table, register st_data_t key, st_data_t value, hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + if (check_upacked(table, hash_val, key)) { + UPVAL_SET(table, value); + return 1; + } + key = (*func)(key); + add_upacked_direct(table, key, value, hash_val); + return 0; + } + if (table->entries_packed) { st_index_t i = find_packed_index(table, hash_val, key); if (i < table->real_entries) { @@ -617,6 +707,11 @@ st_add_direct(st_table *table, st_data_t key, st_data_t value) st_index_t hash_val; hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + add_upacked_direct(table, key, value, hash_val); + return; + } + if (table->entries_packed) { add_packed_direct(table, key, value, hash_val); return; @@ -659,6 +754,10 @@ st_copy(st_table *old_table) } *new_table = *old_table; + if (ULTRA_PACKED(old_table)) { + return new_table; + } + new_table->bins = st_alloc_bins(num_bins); if (new_table->bins == 0) { @@ -695,7 +794,7 @@ st_copy(st_table *old_table) } static inline void -remove_entry(st_table *table, st_table_entry *ptr) +unlink_entry(st_table *table, st_table_entry *ptr) { if (ptr->fore == 0 && ptr->back == 0) { table->head = 0; @@ -708,9 +807,41 @@ remove_entry(st_table *table, st_table_entry *ptr) if (ptr == table->head) table->head = fore; if (ptr == table->tail) table->tail = back; } +} + +static inline void +remove_entry(st_table *table, st_table_entry *ptr) +{ + unlink_entry(table, ptr); table->num_entries--; } +static inline int +delete_packed_entry(st_table *table, st_index_t i, st_data_t *key, st_data_t *value) +{ + if (value != 0) *value = PVAL(table, i); + if (key != 0) *key = PKEY(table, i); + if (table->safe_mode) + PMARK_DELETED(table, i); + else + remove_packed_entry(table, i); + return 1; +} + +static inline int +delete_upacked_entry(st_table *table, st_data_t *key, st_data_t *value) +{ + if (value != 0) *value = UPVAL(table); + if (key != 0) *key = UPKEY(table); + if (table->safe_mode) { + UPMARK_DELETED(table); + } + else { + remove_upacked_entry(table); + } + return 1; +} + int st_delete(register st_table *table, register st_data_t *key, st_data_t *value) { @@ -720,86 +851,112 @@ st_delete(register st_table *table, register st_data_t *key, st_data_t *value) hash_val = do_hash(*key, table); + if (ULTRA_PACKED(table)) { + if (check_upacked(table, hash_val, *key)) { + return delete_upacked_entry(table, key, value); + } + goto not_found; + } + if (table->entries_packed) { st_index_t i = find_packed_index(table, hash_val, *key); - if (i < table->num_entries) { - if (value != 0) *value = PVAL(table, i); - *key = PKEY(table, i); - remove_packed_entry(table, i); - return 1; + if (i < table->real_entries) { + return delete_packed_entry(table, i, key, value); } - if (value != 0) *value = 0; - return 0; + goto not_found; } prev = &table->bins[hash_val % table->num_bins]; for (;(ptr = *prev) != 0; prev = &ptr->next) { - if (EQUAL(table, *key, ptr->key)) { - *prev = ptr->next; - remove_entry(table, ptr); + if (hash_val == ptr->hash && EQUAL(table, *key, ptr->key)) { if (value != 0) *value = ptr->record; *key = ptr->key; - st_free_entry(ptr); + if (table->safe_mode) { + MARK_DELETED(table, ptr); + } + else { + *prev = ptr->next; + remove_entry(table, ptr); + st_free_entry(ptr); + } return 1; } } +not_found: if (value != 0) *value = 0; return 0; } int -st_delete_safe(register st_table *table, register st_data_t *key, st_data_t *value, st_data_t never) +st_shift(register st_table *table, st_data_t *key, st_data_t *value) { - st_index_t hash_val; register st_table_entry *ptr; + if (table->num_entries == 0) { + goto not_found; + } - hash_val = do_hash(*key, table); + if (ULTRA_PACKED(table)) { + return delete_upacked_entry(table, key, value); + } if (table->entries_packed) { - st_index_t i = find_packed_index(table, hash_val, *key); + register st_index_t i = 0; + + while(i < table->real_entries && PDELETED(table, i)) i++; if (i < table->real_entries) { - if (value != 0) *value = PVAL(table, i); - *key = PKEY(table, i); - PKEY_SET(table, i, never); - PVAL_SET(table, i, never); - PHASH_SET(table, i, 0); - table->num_entries--; - return 1; + return delete_packed_entry(table, i, key, value); } - if (value != 0) *value = 0; - return 0; + goto not_found; } - ptr = table->bins[hash_val % table->num_bins]; + for(ptr = table->head; ptr && DELETED(ptr); ptr = ptr->fore); + if (ptr) { + if (value != 0) *value = ptr->record; + if (key != 0) *key = ptr->key; + if (table->safe_mode) { + MARK_DELETED(table, ptr); + } + else { + st_table_entry **tmp; + tmp = &table->bins[ptr->hash % table->num_bins]; - for (; ptr != 0; ptr = ptr->next) { - if ((ptr->key != never) && EQUAL(table, ptr->key, *key)) { + while(*tmp != ptr) tmp = &(*tmp)->next; + *tmp = ptr->next; remove_entry(table, ptr); - *key = ptr->key; - if (value != 0) *value = ptr->record; - ptr->key = ptr->record = never; - return 1; + st_free_entry(ptr); } + return 1; } +not_found: if (value != 0) *value = 0; + if (key != 0) *key = 0; return 0; } void -st_cleanup_safe(st_table *table, st_data_t never) +st_cleanup_safe(st_table *table) { st_table_entry *ptr, **last, *tmp; st_index_t i; + table->safe_mode = ST_REGULAR; + + if (ULTRA_PACKED(table)) { + if (UPDELETED(table)) { + remove_upacked_entry(table); + } + return; + } + if (table->entries_packed) { st_index_t i = 0, j = 0; - while (PKEY(table, i) != never) { + while (!PDELETED(table, i)) { if (i++ == table->real_entries) return; } for (j = i; ++i < table->real_entries;) { - if (PKEY(table, i) == never) continue; + if (PDELETED(table, i)) continue; PACKED_ENT(table, j) = PACKED_ENT(table, i); j++; } @@ -812,9 +969,10 @@ st_cleanup_safe(st_table *table, st_data_t never) for (i = 0; i < table->num_bins; i++) { ptr = *(last = &table->bins[i]); while (ptr != 0) { - if (ptr->key == never) { + if (DELETED(ptr)) { tmp = ptr; *last = ptr = ptr->next; + unlink_entry(table, tmp); st_free_entry(tmp); } else { @@ -827,29 +985,65 @@ st_cleanup_safe(st_table *table, st_data_t never) int st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t *value, st_data_t arg), st_data_t arg) { - st_index_t hash_val, bin_pos; + st_index_t hash_val, bin_pos, i = 0; register st_table_entry *ptr, **last, *tmp; st_data_t value; int retval; hash_val = do_hash(key, table); + if (ULTRA_PACKED(table)) { + if (check_upacked(table, hash_val, key)) { + value = UPVAL(table); + retval = (*func)(key, &value, arg); + if (!ULTRA_PACKED(table)) { + if (PKEY(table, 0) != key) return 0; + goto packed; + } + if (UPKEY(table) != key) return 0; + switch (retval) { + case ST_CONTINUE: + UPVAL_SET(table, value); + break; + case ST_DELETE: + if (table->safe_mode) { + UPMARK_DELETED(table); + } + else { + remove_upacked_entry(table); + } + } + return 1; + } + return 0; + } + if (table->entries_packed) { - st_index_t i = find_packed_index(table, hash_val, key); + i = find_packed_index(table, hash_val, key); if (i < table->real_entries) { value = PVAL(table, i); retval = (*func)(key, &value, arg); + packed: if (!table->entries_packed) { FIND_ENTRY(table, ptr, hash_val, bin_pos); if (ptr == 0) return 0; goto unpacked; } + if (PKEY(table, i) != key) { + i = find_packed_index(table, hash_val, key); + if (i == table->real_entries) return 0; + } switch (retval) { case ST_CONTINUE: PVAL_SET(table, i, value); break; case ST_DELETE: - remove_packed_entry(table, i); + if (table->safe_mode) { + PMARK_DELETED(table, i); + } + else { + remove_packed_entry(table, i); + } } return 1; } @@ -870,6 +1064,10 @@ st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t * ptr->record = value; break; case ST_DELETE: + if (table->safe_mode) { + MARK_DELETED(table, ptr); + break; + } last = &table->bins[bin_pos]; for (; (tmp = *last) != 0; last = &tmp->next) { if (ptr == tmp) { @@ -887,47 +1085,80 @@ st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t * } int -st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t never) +st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg) { st_table_entry *ptr, **last, *tmp; enum st_retval retval; - st_index_t i; + st_index_t i = 0; + st_data_t key, val, hash; + + if (table->num_entries == 0) { + return 0; + } + + if (ULTRA_PACKED(table)) { + if (UPDELETED(table)) return 0; + key = UPKEY(table); + val = UPVAL(table); + hash = UPHASH(table); + retval = (*func)(key, val, arg); + + if (retval == ST_STOP) return 0; + + if (!ULTRA_PACKED(table)) goto packed; + + if (table->ureal_entries == 0 || key != UPKEY(table)) { + return 1; + } + if (retval == ST_DELETE) { + if (table->safe_mode) + UPMARK_DELETED(table); + else + remove_upacked_entry(table); + } + return 0; + } if (table->entries_packed) { for (i = 0; i < table->real_entries; i++) { - st_data_t key, val; - st_index_t hash; + if (PDELETED(table, i)) continue; key = PKEY(table, i); val = PVAL(table, i); hash = PHASH(table, i); - if (key == never) continue; retval = (*func)(key, val, arg); + + if (retval == ST_STOP) return 0; + packed: if (!table->entries_packed) { - FIND_ENTRY(table, ptr, hash, i); - if (retval == ST_CHECK) { - if (!ptr) goto deleted; - goto unpacked_continue; + ptr = table->head; + while(i-- && ptr && ptr->key != key) { + ptr = ptr->next; } - goto unpacked; + + if (!ptr || ptr->key != key) { + return 1; + } + + if (retval == ST_DELETE && !DELETED(ptr)) { + if (table->safe_mode) { + MARK_DELETED(table, ptr); + } + else { + goto unpacked_delete; + } + } + goto unpacked_continue; } - switch (retval) { - case ST_CHECK: /* check if hash is modified during iteration */ - if (PHASH(table, i) == 0 && PKEY(table, i) == never) { - break; + + if (PKEY(table, i) != key) return 1; + if (retval == ST_DELETE) { + if (table->safe_mode) { + PMARK_DELETED(table, i); } - i = find_packed_index(table, hash, key); - if (i == table->real_entries) { - goto deleted; + else { + remove_packed_entry(table, i); + i--; } - /* fall through */ - case ST_CONTINUE: - break; - case ST_STOP: - return 0; - case ST_DELETE: - remove_packed_entry(table, i); - i--; - break; } } return 0; @@ -938,41 +1169,34 @@ st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t if (ptr != 0) { do { - if (ptr->key == never) + if (DELETED(ptr)) goto unpacked_continue; - i = ptr->hash % table->num_bins; + hash = ptr->hash; retval = (*func)(ptr->key, ptr->record, arg); - unpacked: - switch (retval) { - case ST_CHECK: /* check if hash is modified during iteration */ - for (tmp = table->bins[i]; tmp != ptr; tmp = tmp->next) { - if (!tmp) { - deleted: - /* call func with error notice */ - retval = (*func)(0, 0, arg, 1); - return 1; - } + + if (retval == ST_STOP) return 0; + + unpacked_delete: + last = &table->bins[hash % table->num_bins]; + for(;;last = &tmp->next) { + if (!(tmp = *last)) return 1; + if (tmp == ptr) break; + } + + if (retval == ST_DELETE) { + ptr = tmp->fore; + if (table->safe_mode) { + MARK_DELETED(table, tmp); } - /* fall through */ - case ST_CONTINUE: + else { + *last = tmp->next; + remove_entry(table, tmp); + st_free_entry(tmp); + } + } + else { unpacked_continue: ptr = ptr->fore; - break; - case ST_STOP: - return 0; - case ST_DELETE: - last = &table->bins[ptr->hash % table->num_bins]; - for (; (tmp = *last) != 0; last = &tmp->next) { - if (ptr == tmp) { - tmp = ptr->fore; - *last = ptr->next; - remove_entry(table, ptr); - st_free_entry(ptr); - if (ptr == tmp) return 0; - ptr = tmp; - break; - } - } } } while (ptr && table->head); } @@ -984,30 +1208,65 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) { st_table_entry *ptr, **last, *tmp; enum st_retval retval; - st_index_t i; + st_index_t i = 0; + st_data_t key, val, hash; + + if (table->num_entries == 0) { + return 0; + } + + if (ULTRA_PACKED(table)) { + if (UPDELETED(table)) return 0; + key = UPKEY(table); + val = UPVAL(table); + hash = UPHASH(table); + retval = (*func)(key, val, arg); + + if (retval == ST_STOP) return 0; + + if (!ULTRA_PACKED(table)) { + if (PKEY(table, 0) != key) return 0; + goto packed; + } + if (retval == ST_DELETE && UPKEY(table) == key) { + remove_upacked_entry(table); + } + return 0; + } if (table->entries_packed) { for (i = 0; i < table->real_entries; i++) { - st_data_t key, val, hash; + if (PDELETED(table, i)) return 0; key = PKEY(table, i); val = PVAL(table, i); hash = PHASH(table, i); retval = (*func)(key, val, arg); + + if (retval == ST_STOP) return 0; + packed: if (!table->entries_packed) { - FIND_ENTRY(table, ptr, hash, i); - if (!ptr) return 0; - goto unpacked; + last = &table->bins[hash % table->num_bins]; + for(;;last = &ptr->next) { + if (!(ptr = *last)) return 0; + if (ptr->key == key) break; + } + + if (retval == ST_DELETE) { + tmp = ptr; + goto unpacked_delete; + } + goto unpacked_continue; } - switch (retval) { - case ST_CONTINUE: - break; - case ST_CHECK: - case ST_STOP: - return 0; - case ST_DELETE: + + if (retval == ST_DELETE) { + if (key != PKEY(table, i)) { + i = find_packed_index(table, hash, key); + if (i == table->real_entries) { + return 0; + } + } remove_packed_entry(table, i); i--; - break; } } return 0; @@ -1016,33 +1275,31 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) ptr = table->head; } - if (ptr != 0) { - do { - i = ptr->hash % table->num_bins; - retval = (*func)(ptr->key, ptr->record, arg); - unpacked: - switch (retval) { - case ST_CONTINUE: - ptr = ptr->fore; - break; - case ST_CHECK: - case ST_STOP: - return 0; - case ST_DELETE: - last = &table->bins[ptr->hash % table->num_bins]; - for (; (tmp = *last) != 0; last = &tmp->next) { - if (ptr == tmp) { - tmp = ptr->fore; - *last = ptr->next; - remove_entry(table, ptr); - st_free_entry(ptr); - if (ptr == tmp) return 0; - ptr = tmp; - break; - } + while(ptr && table->head) { + if (DELETED(ptr)) { + goto unpacked_continue; + } + hash = ptr->hash; + retval = (*func)(ptr->key, ptr->record, arg); + + if (retval == ST_STOP) return 0; + if (retval == ST_DELETE) { + last = &table->bins[hash % table->num_bins]; + for (; (tmp = *last) != 0; last = &tmp->next) { + if (ptr == tmp) { + unpacked_delete: + ptr = ptr->fore; + *last = tmp->next; + remove_entry(table, tmp); + st_free_entry(tmp); + break; } } - } while (ptr && table->head); + } + else { + unpacked_continue: + ptr = ptr->fore; + } } return 0; } diff --git a/test/-ext-/st/test_numhash.rb b/test/-ext-/st/test_numhash.rb index 24dc87c1d9dfe7..f75ab4e956dff4 100644 --- a/test/-ext-/st/test_numhash.rb +++ b/test/-ext-/st/test_numhash.rb @@ -39,7 +39,7 @@ def test_delete_safe_on_iteration 1.upto(up){|i| tbl[i] = i} assert_nothing_raised("delete_safe forces iteration to fail with size #{up}") do tbl.each do |k, v, t| - assert_equal k, t.delete_safe(k) + assert_equal k, vv = t.delete_safe(k), "delete_safe returns #{vv.inspect} instead of #{k} (initial size #{up})" true end end pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy