mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-compiler: optimize pattern matching for array literals
When the match target is a known array literal, apply these optimizations: 1. Skip #deconstruct call - array literals are already arrays 2. Skip runtime #size check - verify size at compile time 3. Use GETIDX opcode instead of SEND :[] for element access For the general (non-array-literal) case, improve efficiency by: - Using EQ opcode instead of SEND :== for size comparison - Using GE opcode instead of SEND :>= for minimum length check This reduces bytecode size by ~27% for patterns like: [1,2] in Array|[Integer,Integer] Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4302,8 +4302,10 @@ codegen_case(codegen_scope *s, node *varnode, int val)
|
||||
}
|
||||
}
|
||||
|
||||
/* Forward declaration for pattern matching code generation */
|
||||
static void codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos);
|
||||
/* Forward declaration for pattern matching code generation
|
||||
* known_array_len: -1 if unknown, >= 0 if target is known to be an array of that length
|
||||
*/
|
||||
static void codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, int known_array_len);
|
||||
|
||||
/* Pattern matching case/in expression */
|
||||
static void
|
||||
@@ -4317,6 +4319,15 @@ codegen_case_match(codegen_scope *s, node *varnode, int val)
|
||||
uint32_t case_end_jumps = JMPLINK_START;
|
||||
uint32_t tmp;
|
||||
|
||||
/* Check if value is an array literal - allows optimizations in pattern matching */
|
||||
int known_array_len = -1;
|
||||
if (node_type(value) == NODE_ARRAY) {
|
||||
struct mrb_ast_array_node *arr = array_node(value);
|
||||
node *elem;
|
||||
known_array_len = 0;
|
||||
for (elem = arr->elements; elem; elem = elem->cdr) known_array_len++;
|
||||
}
|
||||
|
||||
/* Generate code for the case value */
|
||||
codegen(s, value, VAL);
|
||||
|
||||
@@ -4333,7 +4344,7 @@ codegen_case_match(codegen_scope *s, node *varnode, int val)
|
||||
|
||||
if (pattern) {
|
||||
/* Generate pattern matching code */
|
||||
codegen_pattern(s, pattern, head, &fail_pos);
|
||||
codegen_pattern(s, pattern, head, &fail_pos, known_array_len);
|
||||
}
|
||||
|
||||
/* Generate guard clause if present */
|
||||
@@ -4391,9 +4402,10 @@ codegen_case_match(codegen_scope *s, node *varnode, int val)
|
||||
/* Generate pattern matching code for a single pattern.
|
||||
* target: stack position of the value being matched
|
||||
* fail_pos: linked list of jump positions for pattern match failure
|
||||
* known_array_len: -1 if unknown, >= 0 if target is known to be an array of that length
|
||||
*/
|
||||
static void
|
||||
codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, int known_array_len)
|
||||
{
|
||||
uint32_t tmp;
|
||||
|
||||
@@ -4433,7 +4445,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
uint32_t success_pos = JMPLINK_START;
|
||||
|
||||
/* Try left pattern */
|
||||
codegen_pattern(s, pat_alt->left, target, &left_fail);
|
||||
codegen_pattern(s, pat_alt->left, target, &left_fail, known_array_len);
|
||||
|
||||
/* Optimize: if left_fail is single JMPNOT immediately before here,
|
||||
* convert to JMPIF and skip generating JMP */
|
||||
@@ -4456,7 +4468,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
if (left_fail != JMPLINK_START) {
|
||||
dispatch_linked(s, left_fail);
|
||||
}
|
||||
codegen_pattern(s, pat_alt->right, target, fail_pos);
|
||||
codegen_pattern(s, pat_alt->right, target, fail_pos, known_array_len);
|
||||
|
||||
/* Dispatch success jumps */
|
||||
if (success_pos != JMPLINK_START) {
|
||||
@@ -4469,7 +4481,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
{
|
||||
struct mrb_ast_pat_as_node *pat_as = pat_as_node(pattern);
|
||||
/* First match the pattern */
|
||||
codegen_pattern(s, pat_as->pattern, target, fail_pos);
|
||||
codegen_pattern(s, pat_as->pattern, target, fail_pos, known_array_len);
|
||||
/* Then bind the value to the variable */
|
||||
int idx = lv_idx(s, pat_as->name);
|
||||
if (idx > 0) {
|
||||
@@ -4506,7 +4518,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
{
|
||||
struct mrb_ast_pat_array_node *pat_arr = pat_array_node(pattern);
|
||||
int pre_len = 0, post_len = 0;
|
||||
int arr_reg = cursp();
|
||||
int arr_reg;
|
||||
node *elem;
|
||||
int i;
|
||||
|
||||
@@ -4514,99 +4526,189 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
for (elem = pat_arr->pre; elem; elem = elem->cdr) pre_len++;
|
||||
for (elem = pat_arr->post; elem; elem = elem->cdr) post_len++;
|
||||
|
||||
/* Call deconstruct on target */
|
||||
gen_move(s, cursp(), target, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, arr_reg, sym_idx(s, MRB_SYM_2(s->mrb, deconstruct)), 0);
|
||||
/* Optimization: if we know the target is an array, skip deconstruct */
|
||||
if (known_array_len >= 0) {
|
||||
/* Use target directly as array register */
|
||||
arr_reg = target;
|
||||
/* Compile-time size check */
|
||||
if (pat_arr->rest == 0) {
|
||||
/* No rest: exact length match required */
|
||||
if (known_array_len != pre_len) {
|
||||
/* Size mismatch - always fail */
|
||||
tmp = genjmp(s, OP_JMP, *fail_pos);
|
||||
*fail_pos = tmp;
|
||||
break;
|
||||
}
|
||||
/* Size matches, no runtime check needed */
|
||||
}
|
||||
else {
|
||||
/* Has rest: minimum length check */
|
||||
int min_len = pre_len + post_len;
|
||||
if (known_array_len < min_len) {
|
||||
/* Size too small - always fail */
|
||||
tmp = genjmp(s, OP_JMP, *fail_pos);
|
||||
*fail_pos = tmp;
|
||||
break;
|
||||
}
|
||||
/* Size sufficient, no runtime check needed */
|
||||
}
|
||||
|
||||
/* Check length constraints */
|
||||
if (pat_arr->rest == 0) {
|
||||
/* No rest: exact length match */
|
||||
/* Generate: arr.size == pre_len */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM_2(s->mrb, size)), 0);
|
||||
gen_int(s, cursp(), pre_len);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, eq)), 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
||||
*fail_pos = tmp;
|
||||
/* Match pre-rest elements using GETIDX (faster than SEND :[]) */
|
||||
i = 0;
|
||||
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] using GETIDX */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
genop_1(s, OP_GETIDX, cursp() - 1); /* R[cursp-1] = R[cursp-1][R[cursp]] */
|
||||
/* Element is now at cursp-1 */
|
||||
/* Match element pattern (elements are not known arrays) */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
|
||||
/* Bind rest elements if rest is a variable */
|
||||
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
|
||||
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
|
||||
if (rest_var->name) {
|
||||
int var_idx = lv_idx(s, rest_var->name);
|
||||
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
|
||||
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), pre_len); /* start at cursp */
|
||||
push();
|
||||
if (post_len > 0) {
|
||||
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
|
||||
}
|
||||
else {
|
||||
gen_int(s, cursp(), -1); /* end at cursp */
|
||||
}
|
||||
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
||||
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
||||
/* arr at cursp-2, range at cursp-1 */
|
||||
pop(); /* cursp now at range position */
|
||||
pop(); /* cursp now at arr position */
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
if (var_idx > 0) {
|
||||
gen_move(s, var_idx, cursp(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Match post-rest elements using GETIDX */
|
||||
i = -post_len;
|
||||
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] using GETIDX (negative index from end) */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
genop_1(s, OP_GETIDX, cursp() - 1);
|
||||
/* Match element pattern */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
/* No arr_reg to pop since we used target directly */
|
||||
}
|
||||
else {
|
||||
/* Has rest: minimum length check */
|
||||
int min_len = pre_len + post_len;
|
||||
if (min_len > 0) {
|
||||
/* Generate: arr.size >= min_len */
|
||||
/* General case: need to call deconstruct and check size at runtime */
|
||||
arr_reg = cursp();
|
||||
|
||||
/* Call deconstruct on target */
|
||||
gen_move(s, cursp(), target, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, arr_reg, sym_idx(s, MRB_SYM_2(s->mrb, deconstruct)), 0);
|
||||
|
||||
/* Check length constraints */
|
||||
if (pat_arr->rest == 0) {
|
||||
/* No rest: exact length match */
|
||||
/* Generate: arr.size == pre_len using EQ opcode */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM_2(s->mrb, size)), 0);
|
||||
gen_int(s, cursp(), min_len);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, ge)), 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
||||
gen_int(s, cursp(), pre_len);
|
||||
/* EQ: R[a] = R[a] == R[a+1]; size at cursp-1, pre_len at cursp */
|
||||
genop_1(s, OP_EQ, cursp() - 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp() - 1, *fail_pos, 1);
|
||||
*fail_pos = tmp;
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
/* Match pre-rest elements */
|
||||
i = 0;
|
||||
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
push(); /* Preserve element result for codegen_pattern */
|
||||
/* Match element pattern */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
|
||||
/* Bind rest elements if rest is a variable */
|
||||
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
|
||||
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
|
||||
if (rest_var->name) {
|
||||
int var_idx = lv_idx(s, rest_var->name);
|
||||
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
|
||||
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), pre_len); /* start at cursp */
|
||||
push();
|
||||
if (post_len > 0) {
|
||||
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
|
||||
else {
|
||||
/* Has rest: minimum length check */
|
||||
int min_len = pre_len + post_len;
|
||||
if (min_len > 0) {
|
||||
/* Generate: arr.size >= min_len using GE opcode */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, cursp() - 1, sym_idx(s, MRB_SYM_2(s->mrb, size)), 0);
|
||||
gen_int(s, cursp(), min_len);
|
||||
/* GE: R[a] = R[a] >= R[a+1]; size at cursp-1, min_len at cursp */
|
||||
genop_1(s, OP_GE, cursp() - 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp() - 1, *fail_pos, 1);
|
||||
*fail_pos = tmp;
|
||||
pop();
|
||||
}
|
||||
else {
|
||||
gen_int(s, cursp(), -1); /* end at cursp */
|
||||
}
|
||||
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
||||
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
||||
/* arr at cursp-2, range at cursp-1 */
|
||||
pop(); /* cursp now at range position */
|
||||
pop(); /* cursp now at arr position */
|
||||
}
|
||||
|
||||
/* Match pre-rest elements */
|
||||
i = 0;
|
||||
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
if (var_idx > 0) {
|
||||
gen_move(s, var_idx, cursp(), 1);
|
||||
push(); /* Preserve element result for codegen_pattern */
|
||||
/* Match element pattern */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
|
||||
/* Bind rest elements if rest is a variable */
|
||||
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
|
||||
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
|
||||
if (rest_var->name) {
|
||||
int var_idx = lv_idx(s, rest_var->name);
|
||||
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
|
||||
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), pre_len); /* start at cursp */
|
||||
push();
|
||||
if (post_len > 0) {
|
||||
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
|
||||
}
|
||||
else {
|
||||
gen_int(s, cursp(), -1); /* end at cursp */
|
||||
}
|
||||
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
||||
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
||||
/* arr at cursp-2, range at cursp-1 */
|
||||
pop(); /* cursp now at range position */
|
||||
pop(); /* cursp now at arr position */
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
if (var_idx > 0) {
|
||||
gen_move(s, var_idx, cursp(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Match post-rest elements */
|
||||
i = -post_len;
|
||||
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] (negative index from end) */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
push(); /* Preserve element result for codegen_pattern */
|
||||
/* Match element pattern */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
/* Match post-rest elements */
|
||||
i = -post_len;
|
||||
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
|
||||
/* Get arr[i] (negative index from end) */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), i);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
push(); /* Preserve element result for codegen_pattern */
|
||||
/* Match element pattern */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, fail_pos, -1);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
|
||||
pop(); /* Pop arr_reg */
|
||||
pop(); /* Pop arr_reg */
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4691,7 +4793,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
push(); /* Preserve element result for codegen_pattern */
|
||||
/* Match element pattern - on fail, try next index */
|
||||
codegen_pattern(s, elem->car, cursp() - 1, &match_fail);
|
||||
codegen_pattern(s, elem->car, cursp() - 1, &match_fail, -1);
|
||||
pop(); /* Clean up element slot */
|
||||
}
|
||||
|
||||
@@ -4834,7 +4936,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
push(); /* Preserve value for codegen_pattern */
|
||||
|
||||
/* Match pattern against value */
|
||||
codegen_pattern(s, pat, cursp() - 1, fail_pos);
|
||||
codegen_pattern(s, pat, cursp() - 1, fail_pos, -1);
|
||||
pop(); /* Clean up value slot */
|
||||
}
|
||||
|
||||
@@ -6387,6 +6489,7 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
/* One-line pattern matching: expr in pattern / expr => pattern */
|
||||
struct mrb_ast_match_pat_node *mp = match_pat_node(tree);
|
||||
int head;
|
||||
int known_array_len = -1;
|
||||
uint32_t fail_pos = JMPLINK_START;
|
||||
|
||||
/* Optimize: for simple variable pattern, generate value directly into variable */
|
||||
@@ -6432,7 +6535,7 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
genop_1(s, OP_GETIDX, cursp() - 2); /* R[a] = R[a][R[a+1]] */
|
||||
pop();
|
||||
/* Match element pattern (element is now at cursp()-1) */
|
||||
codegen_pattern(s, e->car, cursp() - 1, &fail_pos);
|
||||
codegen_pattern(s, e->car, cursp() - 1, &fail_pos, -1);
|
||||
pop(); /* clean up array copy slot */
|
||||
}
|
||||
pop(); /* pop array */
|
||||
@@ -6451,11 +6554,19 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
|
||||
head = cursp();
|
||||
|
||||
/* Check if value is array literal for optimization */
|
||||
if (node_type(mp->value) == NODE_ARRAY) {
|
||||
struct mrb_ast_array_node *arr = array_node(mp->value);
|
||||
node *elem;
|
||||
known_array_len = 0;
|
||||
for (elem = arr->elements; elem; elem = elem->cdr) known_array_len++;
|
||||
}
|
||||
|
||||
/* Evaluate the value */
|
||||
codegen(s, mp->value, VAL);
|
||||
|
||||
/* Generate pattern matching code */
|
||||
codegen_pattern(s, mp->pattern, head, &fail_pos);
|
||||
codegen_pattern(s, mp->pattern, head, &fail_pos, known_array_len);
|
||||
|
||||
/* Pattern matched */
|
||||
pop(); /* pop the value */
|
||||
|
||||
Reference in New Issue
Block a user