summaryrefslogtreecommitdiff
path: root/prism_compile.c
diff options
context:
space:
mode:
-rw-r--r--prism_compile.c642
1 files changed, 325 insertions, 317 deletions
@@ -5097,63 +5097,6 @@ pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co
}
/**
- * When we're compiling a case node, it's possible that we can speed it up using
- * a dis hash, which will allow us to jump directly to the correct when
- * clause body based on a hash lookup of the value. This can only happen when
- * the conditions are literals that can be compiled into a hash key.
- *
- * This function accepts a dis hash and the condition of a when clause. It
- * is responsible for compiling the condition into a hash key and then adding it
- * to the dis hash.
- *
- * If the value can be successfully compiled into the hash, then this function
- * returns the dis hash with the new key added. If the value cannot be
- * compiled into the hash, then this function returns Qundef. In the case of
- * Qundef, this function is signaling that the caller should abandon the
- * optimization entirely.
- */
-static VALUE
-pm_compile_case_node_dis(rb_iseq_t *iseq, VALUE dis, const pm_node_t *node, LABEL *label, const pm_scope_node_t *scope_node)
-{
- VALUE key = Qundef;
-
- switch (PM_NODE_TYPE(node)) {
- case PM_FLOAT_NODE: {
- key = pm_static_literal_value(iseq, node, scope_node);
- double intptr;
-
- if (modf(RFLOAT_VALUE(key), &intptr) == 0.0) {
- key = (FIXABLE(intptr) ? LONG2FIX((long) intptr) : rb_dbl2big(intptr));
- }
-
- break;
- }
- case PM_FALSE_NODE:
- case PM_INTEGER_NODE:
- case PM_NIL_NODE:
- case PM_SOURCE_FILE_NODE:
- case PM_SOURCE_LINE_NODE:
- case PM_SYMBOL_NODE:
- case PM_TRUE_NODE:
- key = pm_static_literal_value(iseq, node, scope_node);
- break;
- case PM_STRING_NODE: {
- const pm_string_node_t *cast = (const pm_string_node_t *) node;
- key = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped);
- break;
- }
- default:
- return Qundef;
- }
-
- if (NIL_P(rb_hash_lookup(dis, key))) {
- rb_hash_aset(dis, key, ((VALUE) label) | 1);
- }
-
- return dis;
-}
-
-/**
* Return the object that will be pushed onto the stack for the given node.
*/
static VALUE
@@ -5643,6 +5586,329 @@ pm_compile_constant_path_operator_write_node(rb_iseq_t *iseq, const pm_constant_
}
/**
* Many nodes in Prism can be marked as a static literal, which means slightly
* different things depending on which node it is. Occasionally we need to omit
* container nodes from static literal checks, which is where this macro comes
@@ -6256,269 +6522,11 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
return;
}
- case PM_CASE_NODE: {
// case foo; when bar; end
// ^^^^^^^^^^^^^^^^^^^^^^^
- const pm_case_node_t *cast = (const pm_case_node_t *) node;
- const pm_node_list_t *conditions = &cast->conditions;
-
- // This is the anchor that we will compile the conditions of the various
- // `when` nodes into. If a match is found, they will need to jump into
- // the body_seq anchor to the correct spot.
- DECL_ANCHOR(cond_seq);
- INIT_ANCHOR(cond_seq);
-
- // This is the anchor that we will compile the bodies of the various
- // `when` nodes into. We'll make sure that the clauses that are compiled
- // jump into the correct spots within this anchor.
- DECL_ANCHOR(body_seq);
- INIT_ANCHOR(body_seq);
-
- // This is the label where all of the when clauses will jump to if they
- // have matched and are done executing their bodies.
- LABEL *end_label = NEW_LABEL(location.line);
-
- // If we have a predicate on this case statement, then it's going to
- // compare all of the various when clauses to the predicate. If we
- // don't, then it's basically an if-elsif-else chain.
- if (cast->predicate == NULL) {
- // Establish branch coverage for the case node.
- VALUE branches = Qfalse;
- rb_code_location_t case_location = { 0 };
- int branch_id = 0;
-
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- case_location = pm_code_location(scope_node, (const pm_node_t *) cast);
- branches = decl_branch_base(iseq, PTR2NUM(cast), &case_location, "case");
- }
-
- // Loop through each clauses in the case node and compile each of
- // the conditions within them into cond_seq. If they match, they
- // should jump into their respective bodies in body_seq.
- for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) {
- const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index];
- const pm_node_list_t *conditions = &clause->conditions;
-
- int clause_lineno = pm_node_line_number(parser, (const pm_node_t *) clause);
- LABEL *label = NEW_LABEL(clause_lineno);
- PUSH_LABEL(body_seq, label);
-
- // Establish branch coverage for the when clause.
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- rb_code_location_t branch_location = pm_code_location(scope_node, clause->statements != NULL ? ((const pm_node_t *) clause->statements) : ((const pm_node_t *) clause));
- add_trace_branch_coverage(iseq, body_seq, &branch_location, branch_location.beg_pos.column, branch_id++, "when", branches);
- }
-
- if (clause->statements != NULL) {
- pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node);
- }
- else if (!popped) {
- PUSH_SYNTHETIC_PUTNIL(body_seq, iseq);
- }
-
- PUSH_INSNL(body_seq, location, jump, end_label);
-
- // Compile each of the conditions for the when clause into the
- // cond_seq. Each one should have a unique condition and should
- // jump to the subsequent one if it doesn't match.
- for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) {
- const pm_node_t *condition = conditions->nodes[condition_index];
-
- if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) {
- pm_node_location_t cond_location = PM_NODE_START_LOCATION(parser, condition);
- PUSH_INSN(cond_seq, cond_location, putnil);
- pm_compile_node(iseq, condition, cond_seq, false, scope_node);
- PUSH_INSN1(cond_seq, cond_location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY));
- PUSH_INSNL(cond_seq, cond_location, branchif, label);
- }
- else {
- LABEL *next_label = NEW_LABEL(pm_node_line_number(parser, condition));
- pm_compile_branch_condition(iseq, cond_seq, condition, label, next_label, false, scope_node);
- PUSH_LABEL(cond_seq, next_label);
- }
- }
- }
-
- // Establish branch coverage for the else clause (implicit or
- // explicit).
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- rb_code_location_t branch_location;
-
- if (cast->else_clause == NULL) {
- branch_location = case_location;
- } else if (cast->else_clause->statements == NULL) {
- branch_location = pm_code_location(scope_node, (const pm_node_t *) cast->else_clause);
- } else {
- branch_location = pm_code_location(scope_node, (const pm_node_t *) cast->else_clause->statements);
- }
-
- add_trace_branch_coverage(iseq, cond_seq, &branch_location, branch_location.beg_pos.column, branch_id, "else", branches);
- }
-
- // Compile the else clause if there is one.
- if (cast->else_clause != NULL) {
- pm_compile_node(iseq, (const pm_node_t *) cast->else_clause, cond_seq, popped, scope_node);
- }
- else if (!popped) {
- PUSH_SYNTHETIC_PUTNIL(cond_seq, iseq);
- }
-
- // Finally, jump to the end label if none of the other conditions
- // have matched.
- PUSH_INSNL(cond_seq, location, jump, end_label);
- PUSH_SEQ(ret, cond_seq);
- }
- else {
- // Establish branch coverage for the case node.
- VALUE branches = Qfalse;
- rb_code_location_t case_location = { 0 };
- int branch_id = 0;
-
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- case_location = pm_code_location(scope_node, (const pm_node_t *) cast);
- branches = decl_branch_base(iseq, PTR2NUM(cast), &case_location, "case");
- }
-
- // This is the label where everything will fall into if none of the
- // conditions matched.
- LABEL *else_label = NEW_LABEL(location.line);
-
- // It's possible for us to speed up the case node by using a
- // dis hash. This is a hash that maps the conditions of the
- // various when clauses to the labels of their bodies. If we can
- // compile the conditions into a hash key, then we can use a hash
- // lookup to jump directly to the correct when clause body.
- VALUE dis = Qundef;
- if (ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) {
- dis = rb_hash_new();
- RHASH_TBL_RAW(dis)->type = &cdhash_type;
- }
-
- // We're going to loop through each of the conditions in the case
- // node and compile each of their contents into both the cond_seq
- // and the body_seq. Each condition will use its own label to jump
- // from its conditions into its body.
- //
- // Note that none of the code in the loop below should be adding
- // anything to ret, as we're going to be laying out the entire case
- // node instructions later.
- for (size_t clause_index = 0; clause_index < conditions->size; clause_index++) {
- const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index];
- pm_node_location_t clause_location = PM_NODE_START_LOCATION(parser, (const pm_node_t *) clause);
-
- const pm_node_list_t *conditions = &clause->conditions;
- LABEL *label = NEW_LABEL(clause_location.line);
-
- // Compile each of the conditions for the when clause into the
- // cond_seq. Each one should have a unique comparison that then
- // jumps into the body if it matches.
- for (size_t condition_index = 0; condition_index < conditions->size; condition_index++) {
- const pm_node_t *condition = conditions->nodes[condition_index];
- const pm_node_location_t condition_location = PM_NODE_START_LOCATION(parser, condition);
-
- // If we haven't already abandoned the optimization, then
- // we're going to try to compile the condition into the
- // dis hash.
- if (dis != Qundef) {
- dis = pm_compile_case_node_dis(iseq, dis, condition, label, scope_node);
- }
-
- if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) {
- PUSH_INSN(cond_seq, condition_location, dup);
- pm_compile_node(iseq, condition, cond_seq, false, scope_node);
- PUSH_INSN1(cond_seq, condition_location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY));
- }
- else {
- if (PM_NODE_TYPE_P(condition, PM_STRING_NODE)) {
- const pm_string_node_t *string = (const pm_string_node_t *) condition;
- VALUE value = parse_static_literal_string(iseq, scope_node, condition, &string->unescaped);
- PUSH_INSN1(cond_seq, condition_location, putobject, value);
- }
- else {
- pm_compile_node(iseq, condition, cond_seq, false, scope_node);
- }
-
- PUSH_INSN1(cond_seq, condition_location, topn, INT2FIX(1));
- PUSH_SEND_WITH_FLAG(cond_seq, condition_location, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE));
- }
-
- PUSH_INSNL(cond_seq, condition_location, branchif, label);
- }
-
- // Now, add the label to the body and compile the body of the
- // when clause. This involves popping the predicate, compiling
- // the statements to be executed, and then compiling a jump to
- // the end of the case node.
- PUSH_LABEL(body_seq, label);
- PUSH_INSN(body_seq, clause_location, pop);
-
- // Establish branch coverage for the when clause.
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- rb_code_location_t branch_location = pm_code_location(scope_node, clause->statements != NULL ? ((const pm_node_t *) clause->statements) : ((const pm_node_t *) clause));
- add_trace_branch_coverage(iseq, body_seq, &branch_location, branch_location.beg_pos.column, branch_id++, "when", branches);
- }
-
- if (clause->statements != NULL) {
- pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node);
- }
- else if (!popped) {
- PUSH_SYNTHETIC_PUTNIL(body_seq, iseq);
- }
-
- PUSH_INSNL(body_seq, clause_location, jump, end_label);
- }
-
- // Now that we have compiled the conditions and the bodies of the
- // various when clauses, we can compile the predicate, lay out the
- // conditions, compile the fallback subsequent if there is one, and
- // finally put in the bodies of the when clauses.
- PM_COMPILE_NOT_POPPED(cast->predicate);
-
- // If we have a dis hash, then we'll use it here to create the
- // optimization.
- if (dis != Qundef) {
- PUSH_INSN(ret, location, dup);
- PUSH_INSN2(ret, location, opt_case_dis, dis, else_label);
- LABEL_REF(else_label);
- }
-
- PUSH_SEQ(ret, cond_seq);
-
- // Compile either the explicit else clause or an implicit else
- // clause.
- PUSH_LABEL(ret, else_label);
-
- if (cast->else_clause != NULL) {
- pm_node_location_t else_location = PM_NODE_START_LOCATION(parser, cast->else_clause->statements != NULL ? ((const pm_node_t *) cast->else_clause->statements) : ((const pm_node_t *) cast->else_clause));
- PUSH_INSN(ret, else_location, pop);
-
- // Establish branch coverage for the else clause.
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- rb_code_location_t branch_location = pm_code_location(scope_node, cast->else_clause->statements != NULL ? ((const pm_node_t *) cast->else_clause->statements) : ((const pm_node_t *) cast->else_clause));
- add_trace_branch_coverage(iseq, ret, &branch_location, branch_location.beg_pos.column, branch_id, "else", branches);
- }
-
- PM_COMPILE((const pm_node_t *) cast->else_clause);
- PUSH_INSNL(ret, else_location, jump, end_label);
- }
- else {
- PUSH_INSN(ret, location, pop);
-
- // Establish branch coverage for the implicit else clause.
- if (PM_BRANCH_COVERAGE_P(iseq)) {
- add_trace_branch_coverage(iseq, ret, &case_location, case_location.beg_pos.column, branch_id, "else", branches);
- }
-
- if (!popped) PUSH_INSN(ret, location, putnil);
- PUSH_INSNL(ret, location, jump, end_label);
- }
- }
-
- PUSH_SEQ(ret, body_seq);
- PUSH_LABEL(ret, end_label);
-
return;
- }
case PM_CASE_MATCH_NODE: {
// case foo; in bar; end
// ^^^^^^^^^^^^^^^^^^^^^