mruby-compiler: complete NODE_CASE migration to variable-sized nodes

Replace cons-list based case statement implementation with variable-sized
nodes for improved memory efficiency. The new implementation maintains
identical register allocation behavior using the original's proven
"nil-first, align-last" strategy.

Key changes:
- Convert new_case() to create variable-sized mrb_ast_case_node directly
- Replace codegen_case() with gen_case_var() using array iteration
- Apply original register allocation logic to new node structure
- Fix else clause handling in jump dispatch logic

Supports all case statement variants:
- Bare case statements (case when condition)
- Case with values (case expr when condition)
- UPVAR combinations with closure variables
- Splat operations (*case)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-09-06 07:21:56 +09:00
parent d45ec366c5
commit a4754c03b0
4 changed files with 1344 additions and 1376 deletions
+77 -73
View File
@@ -3841,71 +3841,6 @@ codegen_scall(codegen_scope *s, node *tree, int val)
gen_call(s, tree, val, 1);
}
static void
codegen_case(codegen_scope *s, node *tree, int val)
{
int head = 0;
uint32_t pos1, pos2, pos3, tmp;
node *n;
pos3 = JMPLINK_START;
if (tree->car) {
head = cursp();
codegen(s, tree->car, VAL);
}
tree = tree->cdr;
while (tree) {
n = tree->car->car;
pos1 = pos2 = JMPLINK_START;
while (n) {
codegen(s, n->car, VAL);
if (head) {
gen_move(s, cursp(), head, 0);
push(); push(); pop(); pop(); pop();
if (node_to_int(n->car->car) == NODE_SPLAT) {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1);
}
else {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1);
}
}
else {
pop();
}
tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, !head);
pos2 = tmp;
n = n->cdr;
}
if (tree->car->car) {
pos1 = genjmp_0(s, OP_JMP);
dispatch_linked(s, pos2);
}
codegen(s, tree->car->cdr, val);
if (val) pop();
tmp = genjmp(s, OP_JMP, pos3);
pos3 = tmp;
dispatch(s, pos1);
tree = tree->cdr;
}
if (val) {
uint32_t pos = cursp();
genop_1(s, OP_LOADNIL, cursp());
if (pos3 != JMPLINK_START) dispatch_linked(s, pos3);
if (head) pop();
if (cursp() != pos) {
gen_move(s, cursp(), pos, 0);
}
push();
}
else {
if (pos3 != JMPLINK_START) {
dispatch_linked(s, pos3);
}
if (head) {
pop();
}
}
}
static void
codegen_negate(codegen_scope *s, node *tree, int val)
@@ -5006,23 +4941,96 @@ gen_for_var(codegen_scope *s, node *varnode, int val)
static void
gen_case_var(codegen_scope *s, node *varnode, int val)
{
struct mrb_ast_case_node *case_n = case_node_ctrl(varnode);
struct mrb_ast_case_node *case_n = case_node(varnode);
node *value = CASE_NODE_VALUE(case_n);
node *else_body = CASE_NODE_ELSE(case_n);
uint16_t when_count = CASE_NODE_WHEN_COUNT(case_n);
struct mrb_ast_node **when_clauses = CASE_NODE_WHENS(case_n);
/* For now, generate a simple case structure - this can be optimized later */
int head = 0;
uint32_t pos1, pos2, pos3, tmp;
node *n;
pos3 = JMPLINK_START;
/* Handle case value exactly like original */
if (value) {
head = cursp();
codegen(s, value, VAL);
pop();
}
/* Iterate through when clauses array (replacing cons-list traversal) */
for (int i = 0; i < when_count; i++) {
node *when_clause = (node*)when_clauses[i];
if (!when_clause) continue;
/* when_clause is (condition . body) cons node */
node *args = when_clause->car; /* when conditions */
node *body = when_clause->cdr; /* when body */
/* Process when conditions - original logic unchanged */
n = args;
pos1 = pos2 = JMPLINK_START;
while (n) {
codegen(s, n->car, VAL);
if (head) {
gen_move(s, cursp(), head, 0);
push(); push(); pop(); pop(); pop();
if (node_to_int(n->car->car) == NODE_SPLAT) {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1);
}
else {
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1);
}
}
else {
pop();
}
tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, !head);
pos2 = tmp;
n = n->cdr;
}
if (args) {
pos1 = genjmp_0(s, OP_JMP);
dispatch_linked(s, pos2);
}
/* Generate when body - original logic unchanged */
codegen(s, body, val);
if (val) pop();
tmp = genjmp(s, OP_JMP, pos3);
pos3 = tmp;
dispatch(s, pos1);
}
/* Handle else clause like the original - before the final result logic */
if (else_body) {
codegen(s, else_body, val);
if (val) pop();
tmp = genjmp(s, OP_JMP, pos3);
pos3 = tmp;
}
else if (val) {
/* Apply original's "nil-first, align-last" strategy for VAL case */
if (val) {
uint32_t pos = cursp();
genop_1(s, OP_LOADNIL, cursp());
if (pos3 != JMPLINK_START) dispatch_linked(s, pos3);
if (head) pop();
if (cursp() != pos) {
gen_move(s, cursp(), pos, 0);
}
push();
}
else {
/* NOVAL case - original logic unchanged */
if (pos3 != JMPLINK_START) {
dispatch_linked(s, pos3);
}
if (head) {
pop();
}
}
}
/* Definition node codegen functions */
@@ -6325,10 +6333,6 @@ codegen(codegen_scope *s, node *tree, int val)
break;
case NODE_CASE:
codegen_case(s, tree, val);
break;
case NODE_SCOPE:
codegen_scope_node(s, tree, val);
break;
+7 -7
View File
@@ -305,9 +305,9 @@ struct mrb_ast_case_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *value; /* Case value expression */
uint16_t when_count; /* Number of when clauses */
uint16_t flags; /* Case-specific flags */
uint16_t padding; /* Padding for alignment */
struct mrb_ast_node *else_body; /* Else clause (can be NULL) */
struct mrb_ast_node *when_clauses[]; /* Flexible array for when clauses */
struct mrb_ast_node *when_clauses[1]; /* Variable array for when clauses */
};
/* Variable-sized for node */
@@ -419,7 +419,7 @@ struct mrb_ast_super_node {
#define if_node(n) ((struct mrb_ast_if_node*)(n))
#define while_node(n) ((struct mrb_ast_while_node*)(n))
#define until_node(n) ((struct mrb_ast_until_node*)(n))
#define case_node_ctrl(n) ((struct mrb_ast_case_node*)(n))
#define case_node(n) ((struct mrb_ast_case_node*)(n))
#define for_node(n) ((struct mrb_ast_for_node*)(n))
#define asgn_node(n) ((struct mrb_ast_asgn_node*)(n))
#define masgn_node(n) ((struct mrb_ast_masgn_node*)(n))
@@ -466,10 +466,10 @@ struct mrb_ast_super_node {
#define UNTIL_NODE_CONDITION(n) (until_node(n)->condition)
#define UNTIL_NODE_BODY(n) (until_node(n)->body)
#define CASE_NODE_VALUE(n) (case_node_ctrl(n)->value)
#define CASE_NODE_WHEN_COUNT(n) (case_node_ctrl(n)->when_count)
#define CASE_NODE_ELSE(n) (case_node_ctrl(n)->else_body)
#define CASE_NODE_WHENS(n) (case_node_ctrl(n)->when_clauses)
#define CASE_NODE_VALUE(n) (case_node(n)->value)
#define CASE_NODE_WHEN_COUNT(n) (case_node(n)->when_count)
#define CASE_NODE_ELSE(n) (case_node(n)->else_body)
#define CASE_NODE_WHENS(n) (case_node(n)->when_clauses)
#define FOR_NODE_VAR(n) (for_node(n)->var)
#define FOR_NODE_ITERABLE(n) (for_node(n)->iterable)
+37 -55
View File
@@ -598,7 +598,6 @@ new_alias(parser_state *p, mrb_sym a, mrb_sym b)
/* Forward declarations for variable-sized AST node creation functions */
static node* new_array_var(parser_state *p, node *a);
static node* new_hash_var(parser_state *p, node *a);
static node* new_case_var(parser_state *p, node *value, node *when_list);
static node* new_def_var(parser_state *p, mrb_sym name, node *args, node *body);
static node* new_class_var(parser_state *p, node *name, node *superclass, node *body);
static node* new_module_var(parser_state *p, node *name, node *body);
@@ -705,19 +704,46 @@ new_for(parser_state *p, node *v, node *o, node *b)
static node*
new_case(parser_state *p, node *a, node *b)
{
// If variable-sized nodes are enabled, use the specialized creation function
if (p->var_nodes_enabled) {
return new_case_var(p, a, b);
}
node *n = list2((node*)NODE_CASE, a);
node *n2 = n;
uint16_t when_count = 0;
node *else_body = NULL;
node *current_when = b;
void_expr_error(p, a);
while (n2->cdr) {
n2 = n2->cdr;
// First pass: count when clauses and identify else_body
// The when_list is a linked list where each element's car is a (condition . body) cons node.
// The last element's car might be 0, and its cdr is the else_body.
while (current_when) {
node *clause = current_when->car;
if (clause && node_to_int(clause->car) == 0) { // This is the else clause
else_body = clause->cdr;
break; // Else body is always the last
}
when_count++;
current_when = current_when->cdr;
}
n2->cdr = b;
return n;
size_t base_size = sizeof(struct mrb_ast_case_node);
size_t when_clauses_size = when_count * sizeof(struct mrb_ast_node*);
size_t total_size = base_size + when_clauses_size;
enum mrb_ast_size_class class = size_to_class(total_size);
struct mrb_ast_case_node *n = (struct mrb_ast_case_node*)parser_alloc_var(p, total_size, class);
init_var_header(&n->header, p, NODE_CASE, class);
n->value = a;
n->when_count = when_count;
n->else_body = else_body;
// Second pass: copy when clauses into flexible array
current_when = b;
for (int i = 0; i < when_count; i++) {
n->when_clauses[i] = current_when->car; // Each car is a (condition . body) cons node
current_when = current_when->cdr;
}
return cons_head((node*)NODE_VARIABLE, (node*)n);
}
/* (:postexe a) */
@@ -969,50 +995,6 @@ new_sclass_var(parser_state *p, node *obj, node *body)
return cons_head((node*)NODE_VARIABLE, (node*)n);
}
/* Variable-sized case node creation */
static node*
new_case_var(parser_state *p, node *value, node *when_list)
{
uint16_t when_count = 0;
node *else_body = NULL;
node *current_when = when_list;
// First pass: count when clauses and identify else_body
// The when_list is a linked list where each element's car is a (condition . body) cons node.
// The last element's car might be 0, and its cdr is the else_body.
while (current_when) {
node *clause = current_when->car;
if (clause && node_to_int(clause->car) == 0) { // This is the else clause
else_body = clause->cdr;
break; // Else body is always the last
}
when_count++;
current_when = current_when->cdr;
}
size_t base_size = sizeof(struct mrb_ast_case_node);
size_t when_clauses_size = when_count * sizeof(struct mrb_ast_node*);
size_t total_size = base_size + when_clauses_size;
enum mrb_ast_size_class class = size_to_class(total_size);
struct mrb_ast_case_node *n = (struct mrb_ast_case_node*)parser_alloc_var(p, total_size, class);
init_var_header(&n->header, p, NODE_CASE, class);
n->value = value;
n->when_count = when_count;
n->flags = 0; // No specific flags for now
n->else_body = else_body;
// Second pass: copy when clauses into flexible array
current_when = when_list;
for (int i = 0; i < when_count; i++) {
n->when_clauses[i] = current_when->car; // Each car is a (condition . body) cons node
current_when = current_when->cdr;
}
return cons_head((node*)NODE_VARIABLE, (node*)n);
}
/* Variable-sized assignment node creation */
static node*
File diff suppressed because it is too large Load Diff