From 55f0228bf89d5bc4d5f092faa8bc86f8bc6694c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Hasi=C5=84ski?= Date: Mon, 13 Apr 2026 13:11:22 +0200 Subject: [PATCH] Store compressed aspec on cfunc RProc for correct arity/parameters Compress the 24-bit aspec into 13 free flag bits on RProc (bits 0-6 and 14-19) when wrapping cfunc methods. Field widths: req/opt 3 bits (max 7), post/key 2 bits (max 3), rest/kdict/block 1 bit each. Values exceeding the compressed range are clamped and rest is forced to 1. This enables Proc#arity and Proc#parameters to return correct results for cfunc-backed Procs (e.g. from Method#to_proc) with zero memory overhead -- no struct change needed. Closes #6764 --- include/mruby/proc.h | 53 +++++++++++++++++++++++++++++ mrbgems/mruby-method/src/method.c | 5 ++- mrbgems/mruby-method/test/method.rb | 2 +- mrbgems/mruby-proc-ext/src/proc.c | 34 ++++++++++++------ src/proc.c | 13 ++++++- src/vm.c | 12 +++++-- 6 files changed, 102 insertions(+), 17 deletions(-) diff --git a/include/mruby/proc.h b/include/mruby/proc.h index 3a8a4054d..060471d81 100644 --- a/include/mruby/proc.h +++ b/include/mruby/proc.h @@ -102,6 +102,59 @@ struct RProc { #define MRB_PROC_ALIAS 8192 #define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0) +/* Compressed aspec for cfunc procs (13 bits in RProc.flags). + * Uses free bits 0-6 and 14-19 to store a compressed argument spec. + * Layout: block(0) kdict(1) key(2-3) post(4-5) rest(6) opt(14-16) req(17-19) + * Field widths are smaller than the full 24-bit aspec: req/opt max 7, post/key max 3. + * Values exceeding the compressed range are clamped and rest is forced to 1. */ +#define MRB_PROC_CASPEC_MASK 0xfc07fu /* bits 0-6 and 14-19 */ + +static inline uint32_t +mrb_proc_compress_aspec(mrb_aspec aspec) +{ + uint32_t req = MRB_ASPEC_REQ(aspec); + uint32_t opt = MRB_ASPEC_OPT(aspec); + uint32_t rest = MRB_ASPEC_REST(aspec); + uint32_t post = MRB_ASPEC_POST(aspec); + uint32_t key = MRB_ASPEC_KEY(aspec); + uint32_t kdict = MRB_ASPEC_KDICT(aspec); + uint32_t block = MRB_ASPEC_BLOCK(aspec); + + if (req > 7 || opt > 7 || post > 3 || key > 3) { + if (req > 7) req = 7; + if (opt > 7) opt = 7; + if (post > 3) post = 3; + if (key > 3) key = 3; + rest = 1; + } + + return block | (kdict << 1) | (key << 2) | (post << 4) | (rest << 6) + | (opt << 14) | (req << 17); +} + +static inline mrb_aspec +mrb_proc_decompress_caspec(uint32_t flags) +{ + return (((flags >> 17) & 0x7) << 18) /* req */ + | (((flags >> 14) & 0x7) << 13) /* opt */ + | (((flags >> 6) & 0x1) << 12) /* rest */ + | (((flags >> 4) & 0x3) << 7) /* post */ + | (((flags >> 2) & 0x3) << 2) /* key */ + | (((flags >> 1) & 0x1) << 1) /* kdict */ + | (flags & 0x1); /* block */ +} + +static inline void +mrb_proc_set_cfunc_aspec(struct RProc *p, mrb_aspec aspec) +{ + if (aspec == 0) { + p->flags |= MRB_PROC_NOARG; + } + else { + p->flags |= mrb_proc_compress_aspec(aspec); + } +} + #define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v))) struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*); diff --git a/mrbgems/mruby-method/src/method.c b/mrbgems/mruby-method/src/method.c index ad0b63fcf..6d8495b1b 100644 --- a/mrbgems/mruby-method/src/method.c +++ b/mrbgems/mruby-method/src/method.c @@ -85,6 +85,7 @@ method_missing_prepare(mrb_state *mrb, mrb_sym *mid, mrb_value recv, struct RCla const struct RProc *proc; if (MRB_METHOD_FUNC_P(m)) { struct RProc *p = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); + mrb_proc_set_cfunc_aspec(p, MRB_MT_ASPEC(m.flags)); MRB_PROC_SET_TARGET_CLASS(p, *tc); proc = p; } @@ -393,9 +394,7 @@ method_search_vm(mrb_state *mrb, struct RClass **cp, mrb_sym mid) return MRB_METHOD_PROC(m); struct RProc *proc = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); - if (MRB_MT_ASPEC(m.flags) == 0) { - proc->flags |= MRB_PROC_NOARG; - } + mrb_proc_set_cfunc_aspec(proc, MRB_MT_ASPEC(m.flags)); return proc; } diff --git a/mrbgems/mruby-method/test/method.rb b/mrbgems/mruby-method/test/method.rb index 005403bc5..6ab2aa118 100644 --- a/mrbgems/mruby-method/test/method.rb +++ b/mrbgems/mruby-method/test/method.rb @@ -60,7 +60,7 @@ assert 'Method#arity' do assert_equal(-3, method(:mo7).arity) assert_equal(1, method(:ma1).arity) - assert_equal(-1, method(:__send__).arity) + assert_equal(-2, method(:__send__).arity) assert_equal(-1, method(:nothing).arity) end diff --git a/mrbgems/mruby-proc-ext/src/proc.c b/mrbgems/mruby-proc-ext/src/proc.c index 14c42a790..aac49afe0 100644 --- a/mrbgems/mruby-proc-ext/src/proc.c +++ b/mrbgems/mruby-proc-ext/src/proc.c @@ -182,13 +182,27 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) }; int i; const struct RProc *proc = mrb_proc_ptr(self); + mrb_aspec aspec; + mrb_bool has_lv = TRUE; if (MRB_PROC_CFUNC_P(proc)) { - /* TODO: cfunc aspec is not implemented yet - C functions don't store argument spec info */ - return mrb_ary_new(mrb); + uint32_t caspec_bits = proc->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + aspec = mrb_proc_decompress_caspec(caspec_bits); + } + else if (MRB_PROC_NOARG_P(proc)) { + aspec = 0; + } + else { + return mrb_ary_new(mrb); + } + has_lv = FALSE; } - const struct mrb_irep *irep = proc->body.irep; - if (!irep || !irep->lv || *irep->iseq != OP_ENTER) { - return mrb_ary_new(mrb); + else { + const struct mrb_irep *irep = proc->body.irep; + if (!irep || !irep->lv || *irep->iseq != OP_ENTER) { + return mrb_ary_new(mrb); + } + aspec = PEEK_W(irep->iseq+1); } if (!MRB_PROC_STRICT_P(proc)) { @@ -196,7 +210,6 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) parameters_list[3].name = MRB_SYM(opt); } - mrb_aspec aspec = PEEK_W(irep->iseq+1); parameters_list[0].size = MRB_ASPEC_REQ(aspec); parameters_list[1].size = MRB_ASPEC_OPT(aspec); parameters_list[2].size = MRB_ASPEC_REST(aspec); @@ -214,18 +227,19 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) mrb_value krest = mrb_nil_value(); mrb_value block = mrb_nil_value(); + const mrb_sym *lv = has_lv ? proc->body.irep->lv : NULL; for (i = 0, p = parameters_list; p->name; p++) { mrb_value sname = mrb_symbol_value(p->name); for (int j = 0; j < p->size; i++, j++) { mrb_value a = mrb_ary_new(mrb); mrb_ary_push(mrb, a, sname); - if (i < max && irep->lv[i]) { - mrb_ary_push(mrb, a, mrb_symbol_value(irep->lv[i])); + if (lv && i < max && lv[i]) { + mrb_ary_push(mrb, a, mrb_symbol_value(lv[i])); } if (p->name == MRB_SYM(block)) { - if (irep->lv[i+1]) { - mrb_ary_push(mrb, a, mrb_symbol_value(irep->lv[i+1])); + if (lv && lv[i+1]) { + mrb_ary_push(mrb, a, mrb_symbol_value(lv[i+1])); } block = a; continue; } diff --git a/src/proc.c b/src/proc.c index 546d94907..4864cadd1 100644 --- a/src/proc.c +++ b/src/proc.c @@ -412,7 +412,18 @@ mrb_proc_arity(const struct RProc *p) int ma, op, ra, pa, arity; if (MRB_PROC_CFUNC_P(p)) { - /* TODO cfunc aspec not implemented yet */ + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + aspec = mrb_proc_decompress_caspec(caspec_bits); + ma = MRB_ASPEC_REQ(aspec); + op = MRB_ASPEC_OPT(aspec); + ra = MRB_ASPEC_REST(aspec); + pa = MRB_ASPEC_POST(aspec); + return ra || op ? -(ma + pa + 1) : ma + pa; + } + if (MRB_PROC_NOARG_P(p)) { + return 0; + } return -1; } diff --git a/src/vm.c b/src/vm.c index a1f272a52..8ef495d0e 100644 --- a/src/vm.c +++ b/src/vm.c @@ -901,7 +901,11 @@ exec_irep(mrb_state *mrb, mrb_value self, const struct RProc *p) MRB_PROC_RESOLVE_ALIAS(ci, p); CI_PROC_SET(ci, p); if (MRB_PROC_CFUNC_P(p)) { - if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + check_argument_count(mrb, ci, mrb_proc_decompress_caspec(caspec_bits)); + } + else if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { check_argument_count(mrb, ci, 0); } return MRB_PROC_CFUNC(p)(mrb, self); @@ -1052,7 +1056,11 @@ send_method(mrb_state *mrb, mrb_value self, mrb_bool pub) MRB_PROC_RESOLVE_ALIAS(ci, p); CI_PROC_SET(ci, p); if (MRB_PROC_CFUNC_P(p)) { - if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + check_argument_count(mrb, ci, mrb_proc_decompress_caspec(caspec_bits)); + } + else if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { check_argument_count(mrb, ci, 0); } return MRB_PROC_CFUNC(p)(mrb, self);