mruby-compiler: optimize splat of literal arrays in args/literals

- Skip no-op splats of empty array literals (`*[]` / zarray) in
  call argument generation and array literal codegen.
- Inline non-empty literal splat arrays without inner splats
  (e.g. `*[a,b]`) as regular positional args/elements, avoiding
  building a temporary array and ARYCAT.

This removes unnecessary `LOADNIL` + `ARRAY 0` + `ARYCAT` sequences
(e.g. `mruby -ve 'p *[]'`) and reduces temporary allocations while
preserving semantics and evaluation order. Falls back to the generic
path when nested splats are present or counts exceed fixed-arity.

No behavior change intended; only codegen improvements.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-09-26 11:57:59 +09:00
parent af4df6d75d
commit 1cb8d73ede
+56
View File
@@ -2640,6 +2640,43 @@ gen_values(codegen_scope *s, node *t, int val, int limit)
while (t) {
int is_splat = is_splat_node(t->car);
/* Optimization: skip or inline literal splat arrays
* - Empty splat (`*[]`/`*zarray`): contributes nothing; skip.
* - Non-empty literal array with no inner splat (`*[a,b]`): inline
* as normal positional args to avoid building/concatenating arrays.
*/
if (is_splat) {
node *sv = SPLAT_NODE_VALUE(t->car);
if (sv) {
enum node_type nt = get_node_type(sv);
if (nt == NODE_ARRAY) {
struct mrb_ast_array_node *an = array_node(sv);
if (ARRAY_NODE_ELEMENTS(an) == NULL) {
/* empty splat; contributes nothing */
t = t->cdr;
continue;
}
else if (nosplat(ARRAY_NODE_ELEMENTS(an))) {
/* Inline non-empty literal array elements as regular args */
node *e = ARRAY_NODE_ELEMENTS(an);
while (e) {
/* Honor evaluation order */
codegen(s, e->car, val);
n++;
e = e->cdr;
}
t = t->cdr;
continue;
}
}
else if (nt == NODE_ZARRAY) {
/* explicit empty array literal */
t = t->cdr;
continue;
}
}
}
if (is_splat || cursp() >= slimit) { /* flush stack */
pop_n(n);
if (first) {
@@ -3630,6 +3667,25 @@ codegen_array(codegen_scope *s, node *varnode, int val)
struct mrb_ast_node *element = current->car;
int is_splat = is_splat_node(element);
/* Skip splat of an empty literal array: [*[]] => [] without ARYCAT noise */
if (is_splat) {
node *sv = SPLAT_NODE_VALUE(element);
if (sv) {
enum node_type nt = get_node_type(sv);
if (nt == NODE_ARRAY) {
struct mrb_ast_array_node *an = array_node(sv);
if (ARRAY_NODE_ELEMENTS(an) == NULL) {
current = current->cdr;
continue;
}
}
else if (nt == NODE_ZARRAY) {
current = current->cdr;
continue;
}
}
}
if (is_splat || cursp() >= slimit) { /* flush accumulated elements */
if (regular_elements > 0) {
pop_n(regular_elements);