hash.c: add __pat_values() for pattern matching optimization

Add Hash#__pat_values(keys) that returns an array of values if all
keys exist, or false if any key is missing. This replaces per-key
key?() + []() calls (2N hash lookups) with a single method call
(N hash lookups). The compiler generates __pat_values() followed by
array indexing to extract each value for pattern matching.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-17 14:48:32 +09:00
parent 5f429e151a
commit 4e63489d1a
2 changed files with 55 additions and 21 deletions
+24 -21
View File
@@ -4985,31 +4985,34 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos,
genop_3(s, OP_SEND, hash_reg, sym_idx(s, MRB_SYM_2(s->mrb, deconstruct_keys)), 1);
pop();
/* Match each key-pattern pair */
for (pair = pat_hash->pairs; pair; pair = pair->cdr) {
node *key = pair->car->car;
node *pat = pair->car->cdr;
/* Check key existence: hash.key?(key) */
gen_move(s, cursp(), hash_reg, 0);
/* Check all keys exist and get values via __pat_values */
if (num_keys > 0) {
int vals_reg = cursp();
gen_move(s, vals_reg, hash_reg, 0);
push();
gen_pat_key(s, key);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM_Q_2(s->mrb, key)), 1);
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
gen_pat_keys_ary(s, pat_hash->pairs, num_keys);
genop_3(s, OP_SEND, vals_reg, sym_idx(s, MRB_SYM_2(s->mrb, __pat_values)), 1);
pop(); /* keys_ary */
/* vals_reg = values array or false; fail if false */
tmp = genjmp2(s, OP_JMPNOT, vals_reg, *fail_pos, 1);
*fail_pos = tmp;
/* Get value: hash[key] */
gen_move(s, cursp(), hash_reg, 0);
push();
gen_pat_key(s, key);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_OPSYM_2(s->mrb, aref)), 1);
push();
/* Match each value against its pattern */
int i = 0;
for (pair = pat_hash->pairs; pair; pair = pair->cdr, i++) {
node *pat = pair->car->cdr;
/* Match pattern against value */
codegen_pattern(s, pat, cursp() - 1, fail_pos, -1);
pop();
gen_move(s, cursp(), vals_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();
codegen_pattern(s, pat, cursp() - 1, fail_pos, -1);
pop();
}
pop(); /* vals_reg */
}
/* Handle rest pattern */
+31
View File
@@ -2042,6 +2042,36 @@ mrb_hash_compact(mrb_state *mrb, mrb_value hash)
return hash;
}
/*
* Internal method for pattern matching key check + value extraction.
* Returns an array of values if all keys exist, false otherwise.
*
* {a: 1, b: 2}.__pat_values([:a, :b]) #=> [1, 2]
* {a: 1}.__pat_values([:a, :b]) #=> false
*/
static mrb_value
mrb_hash_pat_values(mrb_state *mrb, mrb_value hash)
{
mrb_value keys;
mrb_get_args(mrb, "A", &keys);
const mrb_value *ary = RARRAY_PTR(keys);
mrb_int klen = RARRAY_LEN(keys);
struct RHash *h = mrb_hash_ptr(hash);
mrb_value result = mrb_ary_new_capa(mrb, klen);
int ai = mrb_gc_arena_save(mrb);
for (mrb_int i = 0; i < klen; i++) {
mrb_value val;
if (!h_get(mrb, h, ary[i], &val)) {
return mrb_false_value();
}
mrb_ary_push(mrb, result, val);
mrb_gc_arena_restore(mrb, ai);
}
return result;
}
/*
* Internal method for pattern matching **rest.
* Returns a new hash excluding keys in the given array.
@@ -2314,6 +2344,7 @@ mrb_init_hash(mrb_state *mrb)
mrb_define_method_id(mrb, h, MRB_SYM(rassoc), mrb_hash_rassoc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(__merge), mrb_hash_merge_m, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, h, MRB_SYM(__compact), mrb_hash_compact, MRB_ARGS_NONE()); /* implementation of Hash#compact! */
mrb_define_method_id(mrb, h, MRB_SYM(__pat_values), mrb_hash_pat_values, MRB_ARGS_REQ(1)); /* for pattern matching keys */
mrb_define_method_id(mrb, h, MRB_SYM(__except), mrb_hash_except_keys, MRB_ARGS_REQ(1)); /* for pattern matching **rest */
}
#undef lesser