From bc07b0b9e1d55821acaa0effea67a9885a3bb56d Mon Sep 17 00:00:00 2001 From: Jemma Issroff Date: Tue, 7 Nov 2023 10:10:23 -0300 Subject: [PATCH] [PRISM] Implement compilation for different parameters This commit compiles most parameter types, setting appropriate values on the ISEQ_BODY. It also adds tests for callers and callees of methods, using many versions of tests from bootstraptest --- prism_compile.c | 155 ++++++++++++++++++++++++++------ test/ruby/test_compile_prism.rb | 42 ++++++++- 2 files changed, 168 insertions(+), 29 deletions(-) diff --git a/prism_compile.c b/prism_compile.c index d64c60f588adce..ab3b837626ab09 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1396,12 +1396,11 @@ pm_setup_args(pm_arguments_node_t *arguments_node, int *flags, struct rb_callinf *flags |= VM_CALL_ARGS_SPLAT; pm_splat_node_t *splat_node = (pm_splat_node_t *)argument; if (splat_node->expression) { - PM_COMPILE(splat_node->expression); + orig_argc++; + PM_COMPILE_NOT_POPPED(splat_node->expression); } - if (!popped) { - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); - } + ADD_INSN1(ret, &dummy_line_node, splatarray, popped ? Qfalse : Qtrue); has_splat = true; post_splat_counter = 0; @@ -3258,35 +3257,45 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_constant_id_list_t *locals = &scope_node->locals; pm_parameters_node_t *parameters_node = (pm_parameters_node_t *) scope_node->parameters; + pm_node_list_t *keywords_list = NULL; pm_node_list_t *optionals_list = NULL; + pm_node_list_t *posts_list = NULL; + pm_node_list_t *requireds_list = NULL; + + struct rb_iseq_param_keyword *keyword; + + struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); - if (parameters_node != NULL) { + if (parameters_node) { optionals_list = ¶meters_node->optionals; - ISEQ_BODY(iseq)->param.lead_num = (int) parameters_node->requireds.size; - ISEQ_BODY(iseq)->param.opt_num = (int) optionals_list->size; + requireds_list = ¶meters_node->requireds; + keywords_list = ¶meters_node->keywords; + posts_list = ¶meters_node->posts; } else if (PM_NODE_TYPE_P(scope_node->ast_node, PM_FOR_NODE)) { - ISEQ_BODY(iseq)->param.lead_num = 1; - ISEQ_BODY(iseq)->param.opt_num = 0; + body->param.lead_num = 1; + body->param.opt_num = 0; } else { - ISEQ_BODY(iseq)->param.lead_num = 0; - ISEQ_BODY(iseq)->param.opt_num = 0; + body->param.lead_num = 0; + body->param.opt_num = 0; } - size_t size = locals->size; + size_t locals_size = locals->size; // Index lookup table buffer size is only the number of the locals st_table *index_lookup_table = st_init_numtable(); VALUE idtmp = 0; - rb_ast_id_table_t *tbl = ALLOCV(idtmp, sizeof(rb_ast_id_table_t) + size * sizeof(ID)); - tbl->size = (int) size; + rb_ast_id_table_t *tbl = ALLOCV(idtmp, sizeof(rb_ast_id_table_t) + locals_size * sizeof(ID)); + tbl->size = (int) locals_size; + body->param.size = (int) locals_size; - for (size_t i = 0; i < size; i++) { + for (size_t i = 0; i < locals_size; i++) { pm_constant_id_t constant_id = locals->ids[i]; ID local; if (constant_id & TEMP_CONSTANT_IDENTIFIER) { local = rb_make_temporary_id(i); - } else { + } + else { local = pm_constant_id_lookup(scope_node, constant_id); } tbl->ids[i] = local; @@ -3295,10 +3304,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, scope_node->index_lookup_table = index_lookup_table; - // TODO: Set all the other nums (good comment by lead_num illustrating what they are) - ISEQ_BODY(iseq)->param.size = (unsigned int) size; - - if (optionals_list != NULL) { + if (optionals_list && optionals_list->size) { + body->param.opt_num = (int) optionals_list->size; LABEL **opt_table = (LABEL **)ALLOC_N(VALUE, optionals_list->size + 1); LABEL *label; @@ -3319,13 +3326,97 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, opt_table[optionals_list->size] = label; ADD_LABEL(ret, label); - ISEQ_BODY(iseq)->param.flags.has_opt = TRUE; - ISEQ_BODY(iseq)->param.opt_table = (const VALUE *)opt_table; + body->param.flags.has_opt = TRUE; + body->param.opt_table = (const VALUE *)opt_table; + } + + if (requireds_list && requireds_list->size) { + body->param.lead_num = (int) requireds_list->size; + body->param.flags.has_lead = true; + + for (size_t i = 0; i < requireds_list->size; i++) { + pm_node_t *required_param = requireds_list->nodes[i]; + // TODO: Fix MultiTargetNodes + if (PM_NODE_TYPE_P(required_param, PM_MULTI_TARGET_NODE)) { + PM_COMPILE(required_param); + } + } + } + + if (posts_list && posts_list->size) { + body->param.post_num = (int) posts_list->size; + body->param.post_start = body->param.lead_num + body->param.opt_num; // TODO + body->param.flags.has_post = true; + } + + // Keywords create an internal variable on the parse tree + if (keywords_list && keywords_list->size) { + body->param.keyword = keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1); + keyword->num = (int) keywords_list->size; + + body->param.flags.has_kw = TRUE; + const VALUE default_values = rb_ary_hidden_new(1); + const VALUE complex_mark = rb_str_tmp_new(0); + + for (size_t i = 0; i < keywords_list->size; i++) { + pm_node_t *keyword_parameter_node = keywords_list->nodes[i]; + + switch PM_NODE_TYPE(keyword_parameter_node) { + case PM_OPTIONAL_KEYWORD_PARAMETER_NODE: { + pm_node_t *value = ((pm_optional_keyword_parameter_node_t *)keyword_parameter_node)->value; + + if (pm_static_literal_p(value)) { + rb_ary_push(default_values, pm_static_literal_value(value, scope_node, parser)); + } + else { + PM_COMPILE_POPPED(value); + rb_ary_push(default_values, complex_mark); + } + + break; + } + case PM_REQUIRED_KEYWORD_PARAMETER_NODE: { + keyword->required_num++; + break; + } + default: { + rb_bug("Unexpected keyword parameter node type"); + } + } + } + } + + if (parameters_node) { + if (parameters_node->rest) { + body->param.rest_start = body->param.lead_num + body->param.opt_num; + body->param.flags.has_rest = true; + } + + if (parameters_node->keyword_rest) { + switch (PM_NODE_TYPE(parameters_node->keyword_rest)) { + case PM_NO_KEYWORDS_PARAMETER_NODE: { + body->param.flags.accepts_no_kwarg = true; + break; + } + case PM_KEYWORD_REST_PARAMETER_NODE: { + body->param.flags.has_kwrest = true; + break; + } + default: { + rb_bug("Keyword rest is an unexpected type\n"); + } + } + } + + if (parameters_node->block) { + body->param.block_start = (int) locals_size - 1; + body->param.flags.has_block = true; + } } iseq_set_local_table(iseq, tbl); - switch (ISEQ_BODY(iseq)->type) { + switch (body->type) { case ISEQ_TYPE_BLOCK: { LABEL *start = ISEQ_COMPILE_DATA(iseq)->start_label = NEW_LABEL(0); LABEL *end = ISEQ_COMPILE_DATA(iseq)->end_label = NEW_LABEL(0); @@ -3334,7 +3425,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, end->rescued = LABEL_RESCUE_END; ADD_TRACE(ret, RUBY_EVENT_B_CALL); - NODE dummy_line_node = generate_dummy_line_node(ISEQ_BODY(iseq)->location.first_lineno, -1); + NODE dummy_line_node = generate_dummy_line_node(body->location.first_lineno, -1); ADD_INSN (ret, &dummy_line_node, nop); ADD_LABEL(ret, start); @@ -3349,7 +3440,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_scope_node_t next_scope_node; pm_scope_node_init((pm_node_t *)post_execution_node->statements, &next_scope_node, scope_node, parser); - const rb_iseq_t *block = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(ISEQ_BODY(iseq)->parent_iseq), ISEQ_TYPE_BLOCK, lineno); + const rb_iseq_t *block = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(body->parent_iseq), ISEQ_TYPE_BLOCK, lineno); ADD_CALL_WITH_BLOCK(ret, &dummy_line_node, id_core_set_postexe, INT2FIX(0), block); break; @@ -3372,7 +3463,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ADD_LABEL(ret, end); ADD_TRACE(ret, RUBY_EVENT_B_RETURN); - ISEQ_COMPILE_DATA(iseq)->last_line = ISEQ_BODY(iseq)->location.code_location.end_pos.lineno; + ISEQ_COMPILE_DATA(iseq)->last_line = body->location.code_location.end_pos.lineno; /* wide range catch handler must put at last */ ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, NULL, start); @@ -3631,12 +3722,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_YIELD_NODE: { + pm_yield_node_t *yield_node = (pm_yield_node_t *)node; + unsigned int flag = 0; struct rb_callinfo_kwarg *keywords = NULL; - VALUE argc = INT2FIX(0); + int argc = 0; + + if (yield_node->arguments) { + PM_COMPILE((pm_node_t *)yield_node->arguments); + + argc = (int) yield_node->arguments->arguments.size; + } - ADD_INSN1(ret, &dummy_line_node, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), flag, keywords, FALSE)); + ADD_INSN1(ret, &dummy_line_node, invokeblock, new_callinfo(iseq, 0, argc, flag, keywords, FALSE)); PM_POP_IF_POPPED; diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index f846009c055fe2..aeaa9e32faa3c0 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -639,9 +639,48 @@ class PrismClassB::PrismClassD < PrismClassA::PrismClassC; end ) end + # Many of these tests are versions of tests at bootstraptest/test_method.rb def test_DefNode - assert_prism_eval("def prism_method; end") + assert_prism_eval("def prism_test_def_node; end") assert_prism_eval("a = Object.new; def a.prism_singleton; :ok; end; a.prism_singleton") + assert_prism_eval("def self.prism_test_def_node() 1 end; prism_test_def_node()") + assert_prism_eval("def self.prism_test_def_node(a,b) [a, b] end; prism_test_def_node(1,2)") + assert_prism_eval("def self.prism_test_def_node(a,x=7,y=1) x end; prism_test_def_node(7,1)") + + # rest argument + assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node().inspect") + assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node(1).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,*a) a end; prism_test_def_node(7,7,1,2).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y=7,*a) a end; prism_test_def_node(7).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,z=7,*a) a end; prism_test_def_node(7,7).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,z=7,zz=7,*a) a end; prism_test_def_node(7,7,7).inspect") + + # block argument + assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node{}.class") + assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node().inspect") + assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) b end; prism_test_def_node(7,1).inspect") + assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) c end; prism_test_def_node(7,7,1).inspect") + + # splat + assert_prism_eval("def self.prism_test_def_node(a) a end; prism_test_def_node(*[1])") + assert_prism_eval("def self.prism_test_def_node(x,a) a end; prism_test_def_node(7,*[1])") + assert_prism_eval("def self.prism_test_def_node(x,y,a) a end; prism_test_def_node(7,7,*[1])") + assert_prism_eval("def self.prism_test_def_node(x,y,a,b,c) a end; prism_test_def_node(7,7,*[1,7,7])") + + # recursive call + assert_prism_eval("def self.prism_test_def_node(n) n == 0 ? 1 : prism_test_def_node(n-1) end; prism_test_def_node(5)") + + # instance method + assert_prism_eval("class PrismTestDefNode; def prism_test_def_node() 1 end end; PrismTestDefNode.new.prism_test_def_node") + assert_prism_eval("class PrismTestDefNode; def prism_test_def_node(*a) a end end; PrismTestDefNode.new.prism_test_def_node(1).inspect") + + # block argument + assert_prism_eval(<<-CODE + def self.prism_test_def_node(&block) prism_test_def_node2(&block) end + def self.prism_test_def_node2() yield 1 end + prism_test_def_node2 {|a| a } + CODE + ) end def test_LambdaNode @@ -692,6 +731,7 @@ def test_StatementsNode def test_YieldNode assert_prism_eval("def prism_test_yield_node; yield; end") + assert_prism_eval("def prism_test_yield_node; yield 1, 2; end") end ############################################################################