ruby-changes:53239
From: ko1 <ko1@a...>
Date: Wed, 31 Oct 2018 07:11:57 +0900 (JST)
Subject: [ruby-changes:53239] ko1:r65454 (trunk): support theap for T_HASH. [Feature #14989]
ko1 2018-10-31 07:11:51 +0900 (Wed, 31 Oct 2018) New Revision: 65454 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=65454 Log: support theap for T_HASH. [Feature #14989] * hash.c, internal.h: support theap for small Hash. Introduce RHASH_ARRAY (li_table) besides st_table and small Hash (<=8 entries) are managed by an array data structure. This array data can be managed by theap. If st_table is needed, then converting array data to st_table data. For st_table using code, we prepare "stlike" APIs which accepts hash value and are very similar to st_ APIs. This work is based on the GSoC achievement by tacinight <tacingiht@g...> and refined by ko1. Modified files: trunk/.gdbinit trunk/array.c trunk/class.c trunk/compile.c trunk/debug_counter.h trunk/ext/objspace/objspace.c trunk/gc.c trunk/hash.c trunk/include/ruby/intern.h trunk/include/ruby/ruby.h trunk/include/ruby/st.h trunk/internal.h trunk/marshal.c trunk/mjit_worker.c trunk/process.c trunk/st.c trunk/test/ruby/test_time.rb trunk/thread.c trunk/transient_heap.c trunk/transient_heap.h trunk/vm_eval.c trunk/vm_insnhelper.c Index: vm_insnhelper.c =================================================================== --- vm_insnhelper.c (revision 65453) +++ vm_insnhelper.c (revision 65454) @@ -3327,7 +3327,7 @@ vm_case_dispatch(CDHASH hash, OFFSET els https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L3327 key = FIXABLE(kval) ? LONG2FIX((long)kval) : rb_dbl2big(kval); } } - if (st_lookup(RHASH_TBL_RAW(hash), key, &val)) { + if (rb_hash_stlike_lookup(hash, key, &val)) { return FIX2LONG((VALUE)val); } else { Index: gc.c =================================================================== --- gc.c (revision 65453) +++ gc.c (revision 65454) @@ -2265,23 +2265,43 @@ obj_free(rb_objspace_t *objspace, VALUE https://github.com/ruby/ruby/blob/trunk/gc.c#L2265 rb_ary_free(obj); break; case T_HASH: - if (RANY(obj)->as.hash.ntbl) { #if USE_DEBUG_COUNTER - if (RHASH_SIZE(obj) >= 8) { - RB_DEBUG_COUNTER_INC(obj_hash_ge8); - } - if (RHASH_SIZE(obj) >= 4) { - RB_DEBUG_COUNTER_INC(obj_hash_ge4); - } - else { - RB_DEBUG_COUNTER_INC(obj_hash_under4); - } -#endif - st_free_table(RANY(obj)->as.hash.ntbl); - } + if (RHASH_SIZE(obj) >= 8) { + RB_DEBUG_COUNTER_INC(obj_hash_ge8); + } + else if (RHASH_SIZE(obj) >= 4) { + RB_DEBUG_COUNTER_INC(obj_hash_ge4); + } + else if (RHASH_SIZE(obj) >= 1) { + RB_DEBUG_COUNTER_INC(obj_hash_under4); + } else { RB_DEBUG_COUNTER_INC(obj_hash_empty); } + + if (RHASH_ARRAY_P(obj)) { + RB_DEBUG_COUNTER_INC(obj_hash_array); + } + else { + RB_DEBUG_COUNTER_INC(obj_hash_st); + } +#endif + if (/* RHASH_ARRAY_P(obj) */ !FL_TEST_RAW(obj, RHASH_ST_TABLE_FLAG)) { + li_table *tab = RHASH(obj)->as.li; + + if (tab) { + if (RHASH_TRANSIENT_P(obj)) { + RB_DEBUG_COUNTER_INC(obj_hash_transient); + } + else { + ruby_xfree(tab); + } + } + } + else { + GC_ASSERT(RHASH_TABLE_P(obj)); + st_free_table(RHASH(obj)->as.st); + } break; case T_REGEXP: if (RANY(obj)->as.regexp.ptr) { @@ -3337,8 +3357,12 @@ obj_memsize_of(VALUE obj, int use_all_ty https://github.com/ruby/ruby/blob/trunk/gc.c#L3357 size += rb_ary_memsize(obj); break; case T_HASH: - if (RHASH(obj)->ntbl) { - size += st_memsize(RHASH(obj)->ntbl); + if (RHASH_ARRAY_P(obj)) { + size += sizeof(li_table); + } + else { + VM_ASSERT(RHASH_ST_TABLE(obj) != NULL); + size += st_memsize(RHASH_ST_TABLE(obj)); } break; case T_REGEXP: @@ -3491,7 +3515,7 @@ count_objects(int argc, VALUE *argv, VAL https://github.com/ruby/ruby/blob/trunk/gc.c#L3515 hash = rb_hash_new(); } else if (!RHASH_EMPTY_P(hash)) { - st_foreach(RHASH_TBL_RAW(hash), set_zero, hash); + rb_hash_stlike_foreach(hash, set_zero, hash); } rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total)); rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(freed)); @@ -4225,7 +4249,23 @@ mark_keyvalue(st_data_t key, st_data_t v https://github.com/ruby/ruby/blob/trunk/gc.c#L4249 } static void -mark_hash(rb_objspace_t *objspace, st_table *tbl) +mark_hash(rb_objspace_t *objspace, VALUE hash) +{ + rb_hash_stlike_foreach(hash, mark_keyvalue, (st_data_t)objspace); + + if (RHASH_ARRAY_P(hash)) { + if (objspace->mark_func_data == NULL && RHASH_TRANSIENT_P(hash)) { + rb_transient_heap_mark(hash, RHASH_ARRAY(hash)); + } + } + else { + VM_ASSERT(!RHASH_TRANSIENT_P(hash)); + } + gc_mark(objspace, RHASH(hash)->ifnone); +} + +static void +mark_st(rb_objspace_t *objspace, st_table *tbl) { if (!tbl) return; st_foreach(tbl, mark_keyvalue, (st_data_t)objspace); @@ -4234,7 +4274,7 @@ mark_hash(rb_objspace_t *objspace, st_ta https://github.com/ruby/ruby/blob/trunk/gc.c#L4274 void rb_mark_hash(st_table *tbl) { - mark_hash(&rb_objspace, tbl); + mark_st(&rb_objspace, tbl); } static void @@ -4699,8 +4739,7 @@ gc_mark_children(rb_objspace_t *objspace https://github.com/ruby/ruby/blob/trunk/gc.c#L4739 break; case T_HASH: - mark_hash(objspace, any->as.hash.ntbl); - gc_mark(objspace, any->as.hash.ifnone); + mark_hash(objspace, obj); break; case T_STRING: @@ -9582,6 +9621,13 @@ rb_raw_obj_info(char *buff, const int bu https://github.com/ruby/ruby/blob/trunk/gc.c#L9621 { if (SPECIAL_CONST_P(obj)) { snprintf(buff, buff_size, "%s", obj_type_name(obj)); + + if (FIXNUM_P(obj)) { + snprintf(buff, buff_size, "%s %ld", buff, FIX2LONG(obj)); + } + else if (SYMBOL_P(obj)) { + snprintf(buff, buff_size, "%s %s", buff, rb_id2name(SYM2ID(obj))); + } } else { #define TF(c) ((c) != 0 ? "true" : "false") @@ -9658,6 +9704,13 @@ rb_raw_obj_info(char *buff, const int bu https://github.com/ruby/ruby/blob/trunk/gc.c#L9704 snprintf(buff, buff_size, "%s %s", buff, RSTRING_PTR(obj)); break; } + case T_HASH: { + snprintf(buff, buff_size, "%s [%c%c] %d", buff, + RHASH_ARRAY_P(obj) ? 'A' : 'S', + RHASH_TRANSIENT_P(obj) ? 'T' : ' ', + (int)RHASH_SIZE(obj)); + break; + } case T_CLASS: { VALUE class_path = rb_class_path_cached(obj); if (!NIL_P(class_path)) { Index: .gdbinit =================================================================== --- .gdbinit (revision 65453) +++ .gdbinit (revision 65454) @@ -156,8 +156,12 @@ define rp https://github.com/ruby/ruby/blob/trunk/.gdbinit#L156 else if ($flags & RUBY_T_MASK) == RUBY_T_HASH printf "%sT_HASH%s: ", $color_type, $color_end, - if ((struct RHash *)($arg0))->ntbl - printf "len=%ld ", ((struct RHash *)($arg0))->ntbl->num_entries + if (((struct RHash *)($arg0))->basic->flags & RHASH_ST_TABLE_FLAG) + printf "st len=%ld ", ((struct RHash *)($arg0))->as.st->num_entries + else + printf "li len=%ld bound=%ld ", \ + ((((struct RHash *)($arg0))->basic->flags & RHASH_ARRAY_LEN_MASK) >> RHASH_ARRAY_LEN_SHIFT), \ + ((((struct RHash *)($arg0))->basic->flags & RHASH_ARRAY_BOUND_MASK) >> RHASH_ARRAY_BOUND_SHIFT) end print (struct RHash *)($arg0) else Index: array.c =================================================================== --- array.c (revision 65453) +++ array.c (revision 65454) @@ -4420,11 +4420,11 @@ static inline void https://github.com/ruby/ruby/blob/trunk/array.c#L4420 ary_recycle_hash(VALUE hash) { assert(RBASIC_CLASS(hash) == 0); - if (RHASH(hash)->ntbl) { - st_table *tbl = RHASH(hash)->ntbl; + if (RHASH_TABLE_P(hash)) { + st_table *tbl = RHASH_ST_TABLE(hash); st_free_table(tbl); + RHASH_CLEAR(hash); } - rb_gc_force_recycle(hash); } /* @@ -4467,7 +4467,7 @@ rb_ary_diff(VALUE ary1, VALUE ary2) https://github.com/ruby/ruby/blob/trunk/array.c#L4467 hash = ary_make_hash(ary2); for (i=0; i<RARRAY_LEN(ary1); i++) { - if (st_lookup(rb_hash_tbl_raw(hash), RARRAY_AREF(ary1, i), 0)) continue; + if (rb_hash_stlike_lookup(hash, RARRAY_AREF(ary1, i), NULL)) continue; rb_ary_push(ary3, rb_ary_elt(ary1, i)); } ary_recycle_hash(hash); @@ -4515,7 +4515,7 @@ rb_ary_difference_multi(int argc, VALUE https://github.com/ruby/ruby/blob/trunk/array.c#L4515 VALUE elt = rb_ary_elt(ary, i); for (j = 0; j < argc; j++){ if (is_hash[j]) { - if (st_lookup(rb_hash_tbl_raw(argv[j]), RARRAY_AREF(ary, i), 0)) + if (rb_hash_stlike_lookup(argv[j], RARRAY_AREF(ary, i), NULL)) break; } else { @@ -4551,7 +4551,6 @@ static VALUE https://github.com/ruby/ruby/blob/trunk/array.c#L4551 rb_ary_and(VALUE ary1, VALUE ary2) { VALUE hash, ary3, v; - st_table *table; st_data_t vv; long i; @@ -4570,12 +4569,11 @@ rb_ary_and(VALUE ary1, VALUE ary2) https://github.com/ruby/ruby/blob/trunk/array.c#L4569 } hash = ary_make_hash(ary2); - table = rb_hash_tbl_raw(hash); for (i=0; i<RARRAY_LEN(ary1); i++) { v = RARRAY_AREF(ary1, i); vv = (st_data_t)v; - if (st_delete(table, &vv, 0)) { + if (rb_hash_stlike_delete(hash, &vv, 0)) { rb_ary_push(ary3, v); } } @@ -4609,7 +4607,7 @@ rb_ary_union_hash(VALUE hash, VALUE ary2 https://github.com/ruby/ruby/blob/trunk/array.c#L4607 long i; for (i = 0; i < RARRAY_LEN(ary2); i++) { VALUE elt = RARRAY_AREF(ary2, i); - if (!st_update(RHASH_TBL_RAW(hash), (st_data_t)elt, ary_hash_orset, (st_data_t)elt)) { + if (!rb_hash_stlike_update(hash, (st_data_t)elt, ary_hash_orset, (st_data_t)elt)) { RB_OBJ_WRITTEN(hash, Qundef, elt); } } @@ -4866,7 +4864,7 @@ rb_ary_uniq_bang(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L4864 FL_SET_EMBED(ary); } ary_resize_capa(ary, hash_size); - st_foreach(rb_hash_tbl_raw(hash), push_value, ary); + rb_hash_foreach(hash, push_value, ary); ary_recycle_hash(hash); return ary; Index: ext/objspace/objspace.c =================================================================== --- ext/objspace/objspace.c (revision 65453) +++ ext/objspace/objspace.c (revision 65454) @@ -824,7 +824,7 @@ static int https://github.com/ruby/ruby/blob/trunk/ext/objspace/objspace.c#L824 collect_values_of_values(VALUE category, VALUE category_objects, VALUE categories) { VALUE ary = rb_ary_new(); - st_foreach(rb_hash_tbl(category_objects), collect_values, ary); + rb_hash_foreach(category_objects, collect_values, ary); rb_hash_aset(categories, category, ary); return ST_CONTINUE; } Index: compile.c =================================================================== --- compile.c (revision 65453) +++ compile.c (revision 65454) @@ -5017,7 +5017,7 @@ compile_case(rb_iseq_t *iseq, LINK_ANCHO https://github.com/ruby/ruby/blob/trunk/compile.c#L5017 INIT_ANCHOR(body_seq); INIT_ANCHOR(cond_seq); - rb_hash_tbl_raw(literals)->type = &cdhash_type; + RHASH_TBL_RAW(literals)->type = &cdhash_type; CHECK(COMPILE(head, "case base", node->nd_head)); @@ -7990,7 +7990,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq https://github.com/ruby/ruby/blob/trunk/compile.c#L7990 int i; VALUE map = rb_hash_new_with_size(RARRAY_LEN(op)/2); - rb_hash_tbl_raw(map)->type = &cdhash_type; + RHASH_TBL_RAW(map)->type = &cdhash_type; op = rb_to_array_type(op); for (i=0; i<RARRAY_LEN(op); i+=2) { VALUE key = RARRAY_AREF(op, i); Index: vm_eval.c =================================================================== --- vm_eval.c (revision 65453) +++ vm_eval.c (revision 65454) @@ -2035,10 +2035,9 @@ static void https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L2035 local_var_list_add(const struct local_var_list *vars, ID lid) { if (lid && rb_is_local_id(lid)) { - /* should skip temporary variable */ - st_table *tbl = RHASH_TBL_RAW(vars->tbl); - st_data_t idx = 0; /* tbl->num_entries */ - st_update(tbl, ID2SYM(lid), local_var_list_update, idx); + /* should skip temporary variable */ + st_data_t idx = 0; /* tbl->num_entries */ + rb_hash_stlike_update(vars->tbl, ID2SYM(lid), local_var_list_update, idx); } } Index: include/ruby/ruby.h =================================================================== --- include/ruby/ruby.h (revision 65453) +++ include/ruby/ruby.h (revision 65454) @@ -1088,11 +1088,13 @@ struct RRegexp { https://github.com/ruby/ruby/blob/trunk/include/ruby/ruby.h#L1088 #define RREGEXP_SRC_LEN(r) RSTRING_LEN(RREGEXP(r)->src) #define RREGEXP_SRC_END(r) RSTRING_END(RREGEXP(r)->src) -/* RHASH_TBL allocates st_table if not available. */ -#define RHASH_TBL(h) rb_hash_tbl(h) +/* RHash is defined at internal.h */ +size_t rb_hash_size_num(VALUE hash); + +#define RHASH_TBL(h) rb_hash_tbl(h, __FILE__, __LINE__) #define RHASH_ITER_LEV(h) rb_hash_iter_lev(h) #define RHASH_IFNONE(h) rb_hash_ifnone(h) -#define RHASH_SIZE(h) NUM2SIZET(rb_hash_size(h)) +#define RHASH_SIZE(h) rb_hash_size_num(h) #define RHASH_EMPTY_P(h) (RHASH_SIZE(h) == 0) #define RHASH_SET_IFNONE(h, ifnone) rb_hash_set_ifnone((VALUE)h, ifnone) Index: include/ruby/st.h =================================================================== --- include/ruby/st.h (revision 65453) +++ include/ruby/st.h (revision 65454) @@ -143,7 +143,7 @@ CONSTFUNC(st_index_t st_hash_end(st_inde https://github.com/ruby/ruby/blob/trunk/include/ruby/st.h#L143 CONSTFUNC(st_index_t st_hash_start(st_index_t h)); #define st_hash_start(h) ((st_index_t)(h)) -void rb_hash_bulk_insert(long, const VALUE *, VALUE); +void rb_hash_bulk_insert_into_st_table(long, const VALUE *, VALUE); RUBY_SYMBOL_EXPORT_END Index: include/ruby/intern.h =================================================================== --- include/ruby/intern.h (revision 65453) +++ include/ruby/intern.h (revision 65454) @@ -520,11 +520,12 @@ VALUE rb_hash_delete(VALUE,VALUE); https://github.com/ruby/ruby/blob/trunk/include/ruby/intern.h#L520 VALUE rb_hash_set_ifnone(VALUE hash, VALUE ifnone); typedef VALUE rb_hash_update_func(VALUE newkey, VALUE oldkey, VALUE value); VALUE rb_hash_update_by(VALUE hash1, VALUE hash2, rb_hash_update_func *func); -struct st_table *rb_hash_tbl(VALUE); +struct st_table *rb_hash_tbl(VALUE, const char *file, int line); int rb_path_check(const char*); int rb_env_path_tainted(void); VALUE rb_env_clear(void); VALUE rb_hash_size(VALUE); +void rb_hash_free(VALUE); /* io.c */ #define rb_defout rb_stdout RUBY_EXTERN VALUE rb_fs; Index: test/ruby/test_time.rb =================================================================== --- test/ruby/test_time.rb (revision 65453) +++ test/ruby/test_time.rb (revision 65454) @@ -1138,6 +1138,7 @@ class TestTime < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_time.rb#L1138 case size when 20 then expect = 50 when 40 then expect = 86 + when 48 then expect = 94 else flunk "Unsupported RVALUE_SIZE=#{size}, update test_memsize" end Index: transient_heap.c =================================================================== --- transient_heap.c (revision 65453) +++ transient_heap.c (revision 65454) @@ -353,9 +353,10 @@ rb_transient_heap_alloc(VALUE obj, size_ https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L353 struct transient_heap* theap = transient_heap_get(); size_t size = ROUND_UP(req_size + sizeof(struct transient_alloc_header), TRANSIENT_HEAP_ALLOC_ALIGN); - TH_ASSERT(RB_TYPE_P(obj, T_ARRAY) || + TH_ASSERT(RB_TYPE_P(obj, T_ARRAY) || RB_TYPE_P(obj, T_OBJECT) || - RB_TYPE_P(obj, T_STRUCT)); /* supported types */ + RB_TYPE_P(obj, T_STRUCT) || + RB_TYPE_P(obj, T_HASH)); /* supported types */ if (size > TRANSIENT_HEAP_ALLOC_MAX) { if (TRANSIENT_HEAP_DEBUG >= 3) fprintf(stderr, "rb_transient_heap_alloc: [too big: %ld] %s\n", (long)size, rb_obj_info(obj)); @@ -458,7 +459,6 @@ alloc_header_to_block_verbose(struct tra https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L459 else { return NULL; } - return block; } static struct transient_alloc_header * @@ -508,7 +508,7 @@ void https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L508 rb_transient_heap_mark(VALUE obj, const void *ptr) { struct transient_alloc_header *header = ptr_to_alloc_header(ptr); - + if (header->magic != TRANSIENT_HEAP_ALLOC_MAGIC) rb_bug("rb_transient_heap_mark: wrong header, %s (%p)", rb_obj_info(obj), ptr); if (TRANSIENT_HEAP_DEBUG >= 3) fprintf(stderr, "rb_transient_heap_mark: %s (%p)\n", rb_obj_info(obj), ptr); #if TRANSIENT_HEAP_CHECK_MODE > 0 @@ -522,7 +522,7 @@ rb_transient_heap_mark(VALUE obj, const https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L522 rb_bug("rb_transient_heap_mark: magic is broken"); } else if (header->obj != obj) { - transient_heap_dump(theap); + // transient_heap_dump(theap); rb_bug("rb_transient_heap_mark: unmatch (%s is stored, but %s is given)\n", rb_obj_info(header->obj), rb_obj_info(obj)); } @@ -566,6 +566,15 @@ transient_heap_ptr(VALUE obj, int error) https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L566 ptr = rb_struct_const_heap_ptr(obj); } break; + case T_HASH: + if (RHASH_TRANSIENT_P(obj)) { + TH_ASSERT(RHASH_ARRAY_P(obj)); + ptr = (VALUE *)(RHASH(obj)->as.li); + } + else { + ptr = NULL; + } + break; default: if (error) { rb_bug("transient_heap_ptr: unknown obj %s\n", rb_obj_info(obj)); @@ -657,6 +666,8 @@ transient_heap_block_evacuate(struct tra https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L666 while (marked_index >= 0) { struct transient_alloc_header *header = alloc_header(block, marked_index); VALUE obj = header->obj; + TH_ASSERT(header->magic == TRANSIENT_HEAP_ALLOC_MAGIC); + if (header->magic != TRANSIENT_HEAP_ALLOC_MAGIC) rb_bug("rb_transient_heap_mark: wrong header %s\n", rb_obj_info(obj)); if (TRANSIENT_HEAP_DEBUG >= 3) fprintf(stderr, " * transient_heap_block_evacuate %p %s\n", header, rb_obj_info(obj)); @@ -673,8 +684,11 @@ transient_heap_block_evacuate(struct tra https://github.com/ruby/ruby/blob/trunk/transient_heap.c#L684 case T_STRUCT: rb_struct_transient_heap_evacuate(obj, !TRANSIENT_HEAP_DEBUG_DONT_PROMOTE); break; + case T_HASH: + rb_hash_transient_heap_evacuate(obj, !TRANSIENT_HEAP_DEBUG_DONT_PROMOTE); + break; default: - rb_bug("unsupporeted"); + rb_bug("unsupporeted: %s\n", rb_obj_info(obj)); } header->obj = Qundef; /* for debug */ } Index: thread.c =================================================================== --- thread.c (revision 65453) +++ thread.c (revision 65454) @@ -3492,11 +3492,11 @@ rb_thread_variable_p(VALUE thread, VALUE https://github.com/ruby/ruby/blob/trunk/thread.c#L3492 locals = rb_ivar_get(thread, id_locals); - if (!RHASH(locals)->ntbl) + if (rb_hash_lookup(locals, ID2SYM(id)) != Qnil) { + return Qtrue; + } + else { return Qfalse; - - if (st_lookup(RHASH(locals)->ntbl, ID2SYM(id), 0)) { - return Qtrue; } return Qfalse; @@ -4349,7 +4349,7 @@ rb_clear_coverages(void) https://github.com/ruby/ruby/blob/trunk/thread.c#L4349 { VALUE coverages = rb_get_coverages(); if (RTEST(coverages)) { - st_foreach(rb_hash_tbl_raw(coverages), clear_coverage_i, 0); + rb_hash_foreach(coverages, clear_coverage_i, 0); } } Index: transient_heap.h =================================================================== --- transient_heap.h (revision 65453) +++ transient_heap.h (revision 65454) @@ -32,9 +32,9 @@ void rb_transient_heap_dump(void); https://github.com/ruby/ruby/blob/trunk/transient_heap.h#L32 void rb_transient_heap_verify(void); int rb_transient_heap_managed_ptr_p(const void *ptr); -/* evacuate functions */ +/* evacuate functions for each type */ void rb_ary_transient_heap_evacuate(VALUE ary, int promote); void rb_obj_transient_heap_evacuate(VALUE obj, int promote); +void rb_hash_transient_heap_evacuate(VALUE hash, int promote); void rb_struct_transient_heap_evacuate(VALUE st, int promote); - #endif Index: process.c =================================================================== (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/