vm: add OP_BLKCALL for direct block call without method dispatch

Bypass method dispatch when calling blocks via yield. The new OP_BLKCALL
instruction directly invokes the proc without looking up Proc#call,
resulting in 13-17% faster yield performance.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-01-22 08:39:27 +09:00
parent 44f6cf4b4e
commit 52bee49ad2
5 changed files with 64 additions and 1 deletions
+1
View File
@@ -84,6 +84,7 @@ New super-instructions that fuse common opcode sequences to reduce bytecode size
- `OP_ADDILV`/`OP_SUBILV`: Local variable increment/decrement fusion for `i += n` patterns ([43f64b9](https://github.com/mruby/mruby/commit/43f64b9))
- `OP_RETSELF`: Single-byte instruction for `return self` pattern ([a71db8c](https://github.com/mruby/mruby/commit/a71db8c))
- `OP_MATCHERR`: Pattern matching error with conditional execution for `in` patterns ([944168a](https://github.com/mruby/mruby/commit/944168a), [e9a9ba4](https://github.com/mruby/mruby/commit/e9a9ba4))
- `OP_BLKCALL`: Direct block call for `yield`, bypassing method dispatch (13-17% faster)
# Fixed GitHub Issues
+1
View File
@@ -64,6 +64,7 @@ OPCODE(SSENDB, BBB) /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n
OPCODE(SEND, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
OPCODE(SENDB, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
OPCODE(CALL, Z) /* self.call(*, **, &) (But overlay the current call frame; tailcall) */
OPCODE(BLKCALL, BB) /* R[a] = R[a].call(R[a+1],... ,R[a+b]); direct block call */
OPCODE(SUPER, BB) /* R[a] = super(R[a+1],... ,R[a+b+1]) */
OPCODE(ARGARY, BS) /* R[a] = argument array (16=m5:r1:m5:d1:lv4) */
OPCODE(ENTER, W) /* arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1) */
+8 -1
View File
@@ -5582,7 +5582,14 @@ codegen_yield(codegen_scope *s, node *varnode, int val)
pop_n(n + (nk == 15 ? 1 : nk * 2) + 1);
genop_2S(s, OP_BLKPUSH, cursp(), (ainfo<<4)|(lv & 0xf));
if (sendv) n = CALL_MAXARGS;
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM_2(s->mrb, call)), n|(nk<<4));
if (nk == 0 && n < 15) {
/* fast path: direct block call without method dispatch */
genop_2(s, OP_BLKCALL, cursp(), n);
}
else {
/* fallback: use SEND for keyword args or splat */
genop_3(s, OP_SEND, cursp(), sym_idx(s, MRB_SYM_2(s->mrb, call)), n|(nk<<4));
}
if (val) push();
}
+3
View File
@@ -351,6 +351,9 @@ codedump(mrb_state *mrb, const mrb_irep *irep, FILE *out)
CASE(OP_CALL, Z):
fprintf(out, "CALL\n");
break;
CASE(OP_BLKCALL, BB):
fprintf(out, "BLKCALL\t\tR%d\t%d\n", a, b);
break;
CASE(OP_SUPER, BB):
fprintf(out, "SUPER\t\tR%d\t", a);
print_args(b, out);
+51
View File
@@ -2380,6 +2380,57 @@ RETRY_TRY_BLOCK:
JUMP;
}
CASE(OP_BLKCALL, BB) {
/* Direct block call: R[a] = R[a].call(R[a+1],...,R[a+b]) */
/* Skip method dispatch - directly invoke the proc */
mrb_value recv = regs[a];
const struct RProc *p;
if (mrb_unlikely(!mrb_proc_p(recv))) {
mrb_raisef(mrb, E_TYPE_ERROR, "wrong type %T (expected Proc)", recv);
}
p = mrb_proc_ptr(recv);
/* push callinfo */
ci = cipush(mrb, a, CINFO_DIRECT, NULL, NULL, NULL, 0, b);
ci->cci = CINFO_NONE; /* mark as VM-to-VM call for proper break handling */
/* handle alias */
MRB_PROC_RESOLVE_ALIAS(ci, p);
if (MRB_PROC_ENV_P(p)) {
ci->mid = MRB_PROC_ENV(p)->mid;
}
ci->u.target_class = MRB_PROC_TARGET_CLASS(p);
CI_PROC_SET(ci, p);
if (MRB_PROC_CFUNC_P(p)) {
recv = MRB_PROC_CFUNC(p)(mrb, recv);
mrb_gc_arena_shrink(mrb, ai);
if (mrb_unlikely(mrb->exc)) goto L_RAISE;
ci = cipop(mrb);
ci[1].stack[0] = recv;
irep = ci->proc->body.irep;
}
else {
irep = p->body.irep;
if (!irep) {
ci->stack[0] = mrb_nil_value();
a = 0;
goto L_OP_RETURN_BODY;
}
mrb_int nargs = b + 1; /* args + self */
if (nargs < irep->nregs) {
stack_extend(mrb, irep->nregs);
stack_clear(regs+nargs, irep->nregs-nargs);
}
if (MRB_PROC_ENV_P(p)) {
regs[0] = MRB_PROC_ENV(p)->stack[0];
}
ci->pc = irep->iseq;
}
JUMP;
}
CASE(OP_SUPER, BB) {
mrb_value recv;
struct RClass* target_class = CI_TARGET_CLASS(ci);