Skip to content

Commit

Permalink
[PRISM] Implement compilation for different parameters
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jemmaissroff committed Nov 7, 2023
1 parent d9d0938 commit bc07b0b
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 29 deletions.
155 changes: 127 additions & 28 deletions prism_compile.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = &parameters_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 = &parameters_node->requireds;
keywords_list = &parameters_node->keywords;
posts_list = &parameters_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;
Expand All @@ -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;

Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
42 changes: 41 additions & 1 deletion test/ruby/test_compile_prism.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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

############################################################################
Expand Down

0 comments on commit bc07b0b

Please sign in to comment.