ruby-changes:48371
From: mame <ko1@a...>
Date: Sat, 28 Oct 2017 00:59:11 +0900 (JST)
Subject: [ruby-changes:48371] mame:r60485 (trunk): Manage AST NODEs out of GC
mame 2017-10-28 00:59:02 +0900 (Sat, 28 Oct 2017) New Revision: 60485 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=60485 Log: Manage AST NODEs out of GC NODEs in AST are no longer objects managed by GC. This change will remove the restriction imposed by the GC. For example, a NODE can use more than five words (this is my primary purpose; we want to store the position data for each NODE, for coverage library), or even a NODE can have variable length (some kinds of NODEs have unused fields). To do this, however, we need more work, since Ripper still uses T_NODE objects managed by the GC. The life time of NODEs is more obvious than other kinds of objects; they are created at parsing, and they become disused immediately after compilation. This change releases all NODEs by a few `xfree`s after compilation, so performance will be improved a bit. In extreme example, `eval("x=1;" * 10000000)` runs much faster (40 sec. -> 7.8 sec. on my machine). The most important part of this change is `ast_t` struct, which has three contents: (1) NODE buffer (malloc'ed memory), (2) a reference to the root NODE, and (3) an array that contains objects that must be marked during parsing (such as literal objects). Some functions that had received `NODE*` arguments, must now receive `ast_t*`. * node.c, node.h: defines `ast_t` struct and related operations. * gc.c, internal.h: defines `imemo_ast`. * parse.y: makes `parser_params` struct have a reference to `ast_t`. Instead of `rb_node_newnode`, use `rb_ast_newnode` to create a NODE. * iseq.c, load.c, ruby.c, template/prelude.c.tmpl: modifies some functions to handle `ast_t*` instead of `NODE*`. * test/ruby/test_gc.rb: ad-hoc fix for a failed test. The test assumes GC eden is increased at startup by NODE object creation. However, this change now create no NODE object, so GC eden is not necessarily increased. Modified files: trunk/gc.c trunk/internal.h trunk/iseq.c trunk/load.c trunk/node.c trunk/node.h trunk/parse.y trunk/ruby.c trunk/template/prelude.c.tmpl trunk/test/ruby/test_gc.rb Index: test/ruby/test_gc.rb =================================================================== --- test/ruby/test_gc.rb (revision 60484) +++ test/ruby/test_gc.rb (revision 60485) @@ -290,6 +290,9 @@ class TestGc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_gc.rb#L290 base_length = GC.stat[:heap_eden_pages] (base_length * 500).times{ 'a' } GC.start + base_length = GC.stat[:heap_eden_pages] + (base_length * 500).times{ 'a' } + GC.start assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r, "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})" Index: internal.h =================================================================== --- internal.h (revision 60484) +++ internal.h (revision 60485) @@ -844,7 +844,8 @@ enum imemo_type { https://github.com/ruby/ruby/blob/trunk/internal.h#L844 imemo_memo = 5, imemo_ment = 6, imemo_iseq = 7, - imemo_alloc = 8 + imemo_alloc = 8, + imemo_ast = 9 }; #define IMEMO_MASK 0x0f Index: iseq.c =================================================================== --- iseq.c (revision 60484) +++ iseq.c (revision 60485) @@ -641,9 +641,9 @@ rb_iseq_compile_with_option(VALUE src, V https://github.com/ruby/ruby/blob/trunk/iseq.c#L641 #else # define INITIALIZED /* volatile */ #endif - NODE *(*parse)(VALUE vparser, VALUE fname, VALUE file, int start); + ast_t *(*parse)(VALUE vparser, VALUE fname, VALUE file, int start); int ln; - NODE *INITIALIZED node; + ast_t *INITIALIZED ast; /* safe results first */ make_compile_option(&option, opt); @@ -659,18 +659,20 @@ rb_iseq_compile_with_option(VALUE src, V https://github.com/ruby/ruby/blob/trunk/iseq.c#L659 { const VALUE parser = rb_parser_new(); rb_parser_set_context(parser, base_block, FALSE); - node = (*parse)(parser, file, src, ln); + ast = (*parse)(parser, file, src, ln); } - if (!node) { + if (!ast->root) { + rb_ast_dispose(ast); rb_exc_raise(th->ec->errinfo); } else { INITIALIZED VALUE label = parent ? parent->body->location.label : rb_fstring_cstr("<compiled>"); - iseq = rb_iseq_new_with_opt(node, label, file, realpath, line, + iseq = rb_iseq_new_with_opt(ast->root, label, file, realpath, line, parent, type, &option); + rb_ast_dispose(ast); } return iseq; @@ -851,8 +853,8 @@ static VALUE https://github.com/ruby/ruby/blob/trunk/iseq.c#L853 iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) { VALUE file, line = INT2FIX(1), opt = Qnil; - VALUE parser, f, exc = Qnil; - const NODE *node; + VALUE parser, f, exc = Qnil, ret; + ast_t *ast; rb_compile_option_t option; int i; @@ -869,18 +871,23 @@ iseqw_s_compile_file(int argc, VALUE *ar https://github.com/ruby/ruby/blob/trunk/iseq.c#L871 parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); - node = rb_parser_compile_file_path(parser, file, f, NUM2INT(line)); - if (!node) exc = GET_EC()->errinfo; + ast = rb_parser_compile_file_path(parser, file, f, NUM2INT(line)); + if (!ast->root) exc = GET_EC()->errinfo; rb_io_close(f); - if (!node) rb_exc_raise(exc); + if (!ast->root) { + rb_ast_dispose(ast); + rb_exc_raise(exc); + } make_compile_option(&option, opt); - return iseqw_new(rb_iseq_new_with_opt(node, rb_fstring_cstr("<main>"), - file, - rb_realpath_internal(Qnil, file, 1), - line, NULL, ISEQ_TYPE_TOP, &option)); + ret = iseqw_new(rb_iseq_new_with_opt(ast->root, rb_fstring_cstr("<main>"), + file, + rb_realpath_internal(Qnil, file, 1), + line, NULL, ISEQ_TYPE_TOP, &option)); + rb_ast_dispose(ast); + return ret; } /* Index: load.c =================================================================== --- load.c (revision 60484) +++ load.c (revision 60485) @@ -602,7 +602,7 @@ rb_load_internal0(rb_thread_t *th, VALUE https://github.com/ruby/ruby/blob/trunk/load.c#L602 EC_PUSH_TAG(th->ec); state = EXEC_TAG(); if (state == TAG_NONE) { - NODE *node; + ast_t *ast; const rb_iseq_t *iseq; if ((iseq = rb_iseq_load_iseq(fname)) != NULL) { @@ -611,9 +611,10 @@ rb_load_internal0(rb_thread_t *th, VALUE https://github.com/ruby/ruby/blob/trunk/load.c#L611 else { VALUE parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); - node = (NODE *)rb_parser_load_file(parser, fname); - iseq = rb_iseq_new_top(node, rb_fstring_cstr("<top (required)>"), + ast = (ast_t *)rb_parser_load_file(parser, fname); + iseq = rb_iseq_new_top(ast->root, rb_fstring_cstr("<top (required)>"), fname, rb_realpath_internal(Qnil, fname, 1), NULL); + rb_ast_dispose(ast); } rb_iseq_eval(iseq); } Index: gc.c =================================================================== --- gc.c (revision 60484) +++ gc.c (revision 60485) @@ -434,6 +434,7 @@ typedef struct RVALUE { https://github.com/ruby/ruby/blob/trunk/gc.c#L434 const rb_iseq_t iseq; rb_env_t env; struct rb_imemo_alloc_struct alloc; + ast_t ast; } imemo; struct { struct RBasic basic; @@ -2359,6 +2360,9 @@ obj_free(rb_objspace_t *objspace, VALUE https://github.com/ruby/ruby/blob/trunk/gc.c#L2360 case imemo_alloc: xfree(RANY(obj)->as.imemo.alloc.ptr); break; + case imemo_ast: + rb_ast_free(&RANY(obj)->as.imemo.ast); + break; default: break; } @@ -4540,6 +4544,9 @@ gc_mark_imemo(rb_objspace_t *objspace, V https://github.com/ruby/ruby/blob/trunk/gc.c#L4544 } while ((m = m->next) != NULL); } return; + case imemo_ast: + rb_ast_mark(&RANY(obj)->as.imemo.ast); + return; #if VM_CHECK_MODE > 0 default: VM_UNREACHABLE(gc_mark_imemo); Index: template/prelude.c.tmpl =================================================================== --- template/prelude.c.tmpl (revision 60484) +++ template/prelude.c.tmpl (revision 60485) @@ -121,10 +121,14 @@ prelude_eval(VALUE code, VALUE name, int https://github.com/ruby/ruby/blob/trunk/template/prelude.c.tmpl#L121 FALSE, /* int debug_frozen_string_literal; */ }; - NODE *node = rb_parser_compile_string_path(rb_parser_new(), name, code, line); - if (!node) rb_exc_raise(rb_errinfo()); - rb_iseq_eval(rb_iseq_new_with_opt(node, name, name, Qnil, INT2FIX(line), + ast_t *ast = rb_parser_compile_string_path(rb_parser_new(), name, code, line); + if (!ast->root) { + rb_ast_dispose(ast); + rb_exc_raise(rb_errinfo()); + } + rb_iseq_eval(rb_iseq_new_with_opt(ast->root, name, name, Qnil, INT2FIX(line), NULL, ISEQ_TYPE_TOP, &optimization)); + rb_ast_dispose(ast); } % end Index: ruby.c =================================================================== --- ruby.c (revision 60484) +++ ruby.c (revision 60485) @@ -177,7 +177,7 @@ cmdline_options_init(ruby_cmdline_option https://github.com/ruby/ruby/blob/trunk/ruby.c#L177 return opt; } -static NODE *load_file(VALUE parser, VALUE fname, VALUE f, int script, +static ast_t *load_file(VALUE parser, VALUE fname, VALUE f, int script, ruby_cmdline_options_t *opt); static VALUE open_load_file(VALUE fname_v, int *xflag); static void forbid_setid(const char *, const ruby_cmdline_options_t *); @@ -1461,7 +1461,7 @@ rb_f_chomp(int argc, VALUE *argv) https://github.com/ruby/ruby/blob/trunk/ruby.c#L1461 static VALUE process_options(int argc, char **argv, ruby_cmdline_options_t *opt) { - NODE *tree = 0; + ast_t *ast = 0; VALUE parser; VALUE script_name; const rb_iseq_t *iseq; @@ -1674,12 +1674,12 @@ process_options(int argc, char **argv, r https://github.com/ruby/ruby/blob/trunk/ruby.c#L1674 ruby_set_script_name(progname); rb_parser_set_options(parser, opt->do_print, opt->do_loop, opt->do_line, opt->do_split); - tree = rb_parser_compile_string(parser, opt->script, opt->e_script, 1); + ast = rb_parser_compile_string(parser, opt->script, opt->e_script, 1); } else { VALUE f; f = open_load_file(script_name, &opt->xflag); - tree = load_file(parser, opt->script_name, f, 1, opt); + ast = load_file(parser, opt->script_name, f, 1, opt); } ruby_set_script_name(opt->script_name); if (dump & DUMP_BIT(yydebug)) { @@ -1704,7 +1704,10 @@ process_options(int argc, char **argv, r https://github.com/ruby/ruby/blob/trunk/ruby.c#L1704 rb_enc_set_default_internal(Qnil); rb_stdio_set_default_encoding(); - if (!tree) return Qfalse; + if (!ast->root) { + rb_ast_dispose(ast); + return Qfalse; + } process_sflag(&opt->sflag); opt->xflag = 0; @@ -1723,10 +1726,13 @@ process_options(int argc, char **argv, r https://github.com/ruby/ruby/blob/trunk/ruby.c#L1726 } if (dump & (DUMP_BIT(parsetree)|DUMP_BIT(parsetree_with_comment))) { - rb_io_write(rb_stdout, rb_parser_dump_tree(tree, dump & DUMP_BIT(parsetree_with_comment))); + rb_io_write(rb_stdout, rb_parser_dump_tree(ast->root, dump & DUMP_BIT(parsetree_with_comment))); rb_io_flush(rb_stdout); dump &= ~DUMP_BIT(parsetree)&~DUMP_BIT(parsetree_with_comment); - if (!dump) return Qtrue; + if (!dump) { + rb_ast_dispose(ast); + return Qtrue; + } } { @@ -1740,7 +1746,8 @@ process_options(int argc, char **argv, r https://github.com/ruby/ruby/blob/trunk/ruby.c#L1746 #endif } base_block = toplevel_context(toplevel_binding); - iseq = rb_iseq_new_main(tree, opt->script_name, path, vm_block_iseq(base_block)); + iseq = rb_iseq_new_main(ast->root, opt->script_name, path, vm_block_iseq(base_block)); + rb_ast_dispose(ast); } if (dump & DUMP_BIT(insns)) { @@ -1790,7 +1797,7 @@ load_file_internal(VALUE argp_v) https://github.com/ruby/ruby/blob/trunk/ruby.c#L1797 ruby_cmdline_options_t *opt = argp->opt; VALUE f = argp->f; int line_start = 1; - NODE *tree = 0; + ast_t *ast = 0; rb_encoding *enc; ID set_encoding; @@ -1894,7 +1901,7 @@ load_file_internal(VALUE argp_v) https://github.com/ruby/ruby/blob/trunk/ruby.c#L1901 return (VALUE)rb_parser_compile_string_path(parser, orig_fname, f, line_start); } rb_funcall(f, set_encoding, 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); - tree = rb_parser_compile_file_path(parser, orig_fname, f, line_start); + ast = rb_parser_compile_file_path(parser, orig_fname, f, line_start); rb_funcall(f, set_encoding, 1, rb_parser_encoding(parser)); if (script && rb_parser_end_seen_p(parser)) { /* @@ -1912,7 +1919,7 @@ load_file_internal(VALUE argp_v) https://github.com/ruby/ruby/blob/trunk/ruby.c#L1919 rb_define_global_const("DATA", f); argp->f = Qnil; } - return (VALUE)tree; + return (VALUE)ast; } static VALUE @@ -2004,7 +2011,7 @@ restore_load_file(VALUE arg) https://github.com/ruby/ruby/blob/trunk/ruby.c#L2011 return Qnil; } -static NODE * +static ast_t * load_file(VALUE parser, VALUE fname, VALUE f, int script, ruby_cmdline_options_t *opt) { struct load_file_arg arg; @@ -2013,8 +2020,8 @@ load_file(VALUE parser, VALUE fname, VAL https://github.com/ruby/ruby/blob/trunk/ruby.c#L2020 arg.script = script; arg.opt = opt; arg.f = f; - return (NODE *)rb_ensure(load_file_internal, (VALUE)&arg, - restore_load_file, (VALUE)&arg); + return (ast_t *)rb_ensure(load_file_internal, (VALUE)&arg, + restore_load_file, (VALUE)&arg); } void * Index: parse.y =================================================================== --- parse.y (revision 60484) +++ parse.y (revision 60485) @@ -239,6 +239,7 @@ struct parser_params { https://github.com/ruby/ruby/blob/trunk/parse.y#L239 unsigned int do_chomp: 1; unsigned int do_split: 1; + ast_t *ast; NODE *eval_tree_begin; NODE *eval_tree; VALUE error_buffer; @@ -338,15 +339,27 @@ parser_set_line(NODE *n, int l) https://github.com/ruby/ruby/blob/trunk/parse.y#L339 } static inline void -rb_discard_node(NODE *n) +rb_discard_node_gen(struct parser_params *parser, NODE *n) { +#ifndef RIPPER + rb_ast_delete_node(parser->ast, n); +#else rb_gc_force_recycle((VALUE)n); +#endif } - -#define add_mark_object(obj) (void)(obj) +#define rb_discard_node(n) rb_discard_node_gen(parser, (n)) #ifndef RIPPER static inline void +add_mark_object_gen(struct parser_params *parser, VALUE obj) +{ + if (!SPECIAL_CONST_P(obj)) { + rb_ast_add_mark_object(parser->ast, obj); + } +} +#define add_mark_object(obj) add_mark_object_gen(parser, (obj)) + +static inline void set_line_body(NODE *body, int line) { if (!body) return; @@ -575,6 +588,8 @@ static NODE *parser_heredoc_dedent(struc https://github.com/ruby/ruby/blob/trunk/parse.y#L588 #else /* RIPPER */ #define NODE_RIPPER NODE_CDECL +#define add_mark_object(obj) (void)(obj) + static inline VALUE ripper_new_yylval(ID a, VALUE b, VALUE c) { @@ -2850,6 +2865,7 @@ primary : literal https://github.com/ruby/ruby/blob/trunk/parse.y#L2865 break; } } + add_mark_object((VALUE)rb_imemo_alloc_new((VALUE)tbl, 0, 0, 0)); scope = NEW_NODE(NODE_SCOPE, tbl, $8, args); nd_set_column(scope, @1.first_column); tbl[0] = 1; tbl[1] = id; @@ -5573,52 +5589,55 @@ lex_getline(struct parser_params *parser https://github.com/ruby/ruby/blob/trunk/parse.y#L5589 static const rb_data_type_t parser_data_type; #ifndef RIPPER -static NODE* +static ast_t* parser_compile_string(VALUE vparser, VALUE fname, VALUE s, int line) { struct parser_params *parser; - NODE *node; + ast_t *ast; TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, parser); + parser->ast = ast = rb_ast_new(); + lex_gets = lex_get_str; lex_gets_ptr = 0; lex_input = rb_str_new_frozen(s); lex_pbeg = lex_p = lex_pend = 0; - node = yycompile(parser, fname, line); + ast->root = yycompile(parser, fname, line); + parser->ast = 0; RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return node; + return ast; } -NODE* +ast_t* rb_compile_string(const char *f, VALUE s, int line) { must_be_ascii_compatible(s); return parser_compile_string(rb_parser_new(), rb_filesystem_str_new_cstr(f), s, line); } -NODE* +ast_t* rb_parser_compile_string(VALUE vparser, const char *f, VALUE s, int line) { return rb_parser_compile_string_path(vparser, rb_filesystem_str_new_cstr(f), s, line); } -NODE* +ast_t* rb_parser_compile_string_path(VALUE vparser, VALUE f, VALUE s, int line) { must_be_ascii_compatible(s); return parser_compile_string(vparser, f, s, line); } -NODE* +ast_t* rb_compile_cstr(const char *f, const char *s, int len, int line) { VALUE str = rb_str_new(s, len); return parser_compile_string(rb_parser_new(), rb_filesystem_str_new_cstr(f), str, line); } -NODE* +ast_t* rb_parser_compile_cstr(VALUE vparser, const char *f, const char *s, int len, int line) { VALUE str = rb_str_new(s, len); @@ -5633,7 +5652,7 @@ lex_io_gets(struct parser_params *parser https://github.com/ruby/ruby/blob/trunk/parse.y#L5652 return rb_io_gets_internal(io); } -NODE* +ast_t* rb_compile_file(const char *f, VALUE file, int start) { VALUE vparser = rb_parser_new(); @@ -5641,27 +5660,30 @@ rb_compile_file(const char *f, VALUE fil https://github.com/ruby/ruby/blob/trunk/parse.y#L5660 return rb_parser_compile_file(vparser, f, file, start); } -NODE* +ast_t* rb_parser_compile_file(VALUE vparser, const char *f, VALUE file, int start) { return rb_parser_compile_file_path(vparser, rb_filesystem_str_new_cstr(f), file, start); } -NODE* +ast_t* rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE file, int start) { struct parser_params *parser; - NODE *node; + ast_t *ast; TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, parser); + parser->ast = ast = rb_ast_new(); + lex_gets = lex_io_gets; lex_input = file; lex_pbeg = lex_p = lex_pend = 0; - node = yycompile(parser, fname, start); + ast->root = yycompile(parser, fname, start); + parser->ast = 0; RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return node; + return ast; } #endif /* !RIPPER */ @@ -6160,8 +6182,11 @@ parser_regx_options(struct parser_params https://github.com/ruby/ruby/blob/trunk/parse.y#L6182 } static void -dispose_string(VALUE str) +dispose_string(struct parser_params *parser, VALUE str) { +#ifndef RIPPER + rb_ast_delete_mark_object(parser->ast, str); +#endif rb_str_free(str); rb_gc_force_recycle(str); } @@ -6639,7 +6664,7 @@ parser_heredoc_restore(struct parser_par https://github.com/ruby/ruby/blob/trunk/parse.y#L6664 lex_p = lex_pbeg + here->nd_nth; heredoc_end = ruby_sourceline; ruby_sourceline = nd_line(here); - dispose_string(here->nd_lit); + dispose_string(parser, here->nd_lit); rb_discard_node(here); token_flush(parser); } @@ -6925,7 +6950,7 @@ parser_here_document(struct parser_param https://github.com/ruby/ruby/blob/trunk/parse.y#L6950 } if (nextc() == -1) { if (str) { - dispose_string(str); + dispose_string(parser, str); str = 0; } goto error; @@ -8800,7 +8825,10 @@ yylex(YYSTYPE *lval, YYLTYPE *yylloc, st https://github.com/ruby/ruby/blob/trunk/parse.y#L8825 static NODE* node_newnode(struct parser_params *parser, enum node_type type, VALUE a0, VALUE a1, VALUE a2) { - NODE *n = (rb_node_newnode)(type, a0, a1, a2); + NODE *n = rb_ast_newnode(parser->ast); + + rb_node_init(n, type, a0, a1, a2); + nd_set_line(n, ruby_sourceline); /* mark not cared column to -1 */ nd_set_column(n, -1); @@ -10590,6 +10618,7 @@ new_args_tail_gen(struct parser_params * https://github.com/ruby/ruby/blob/trunk/parse.y#L10618 NODE *node; args = ZALLOC(struct rb_args_info); + add_mark_object((VALUE)rb_imemo_alloc_new((VALUE)args, 0, 0, 0)); node = NEW_NODE(NODE_ARGS, 0, 0, args); nd_set_column(node, column); if (parser->error_p) return node; @@ -10959,6 +10988,9 @@ local_tbl_gen(struct parser_params *pars https://github.com/ruby/ruby/blob/trunk/parse.y#L10988 } if (--j < cnt) REALLOC_N(buf, ID, (cnt = j) + 1); buf[0] = cnt; + + add_mark_object((VALUE)rb_imemo_alloc_new((VALUE)buf, 0, 0, 0)); + return buf; } #endif @@ -11350,18 +11382,16 @@ parser_mark(void *ptr) https://github.com/ruby/ruby/blob/trunk/parse.y#L11382 { struct parser_params *parser = (struct parser_params*)ptr; - rb_gc_mark((VALUE)lex_strterm); rb_gc_mark(lex_input); rb_gc_mark(lex_lastline); rb_gc_mark(lex_nextline); rb_gc_mark(ruby_sourcefile_string); #ifndef RIPPER - rb_gc_mark((VALUE)ruby_eval_tree_begin); - rb_gc_mark((VALUE)ruby_eval_tree); rb_gc_mark(ruby_debug_lines); rb_gc_mark(parser->compile_option); rb_gc_mark(parser->error_buffer); #else + rb_gc_mark((VALUE)lex_strterm); rb_gc_mark(parser->delayed); rb_gc_mark(parser->value); rb_gc_mark(parser->result); Index: node.c =================================================================== --- node.c (revision 60484) +++ node.c (revision 60485) @@ -1211,3 +1211,107 @@ rb_gc_mark_node(NODE *obj) https://github.com/ruby/ruby/blob/trunk/node.c#L1211 } return 0; } + +typedef struct node_buffer_elem_struct { + struct node_buffer_elem_struct *next; + NODE buf[1]; +} node_buffer_elem_t; + +typedef struct node_buffer_struct { + long idx, len; + node_buffer_elem_t *head; + node_buffer_elem_t body; +} node_buffer_t; + +node_buffer_t * +rb_node_buffer_new() +{ + node_buffer_t *nb = xmalloc(sizeof(node_buffer_t) + 16 * sizeof(NODE)); + nb->idx = 0; + nb->len = 16; + nb->head = &nb->body; + nb->head->next = NULL; + return nb; +} + +void +rb_node_buffer_free(node_buffer_t *nb) +{ + node_buffer_elem_t *nbe = nb->he (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/