vm: add OP_RETNIL for returning nil directly

Add a new opcode that returns nil without requiring LOADNIL + RETURN.
This avoids loading nil into a register by setting the return value (v)
directly. The implementation uses a separate label (L_RETURN_NIL) to
bypass v = regs[a], preserving self in regs[0] for ensure blocks.

Codegen applies peephole optimization to fuse LOADNIL + RETURN -> RETNIL.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-01-23 00:33:25 +09:00
parent a6bf08847a
commit 0b1af858e2
5 changed files with 19 additions and 1 deletions
+1
View File
@@ -85,6 +85,7 @@ New super-instructions that fuse common opcode sequences to reduce bytecode size
- `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)
- `OP_RETNIL`: Single-byte instruction for `return nil` pattern ([96641c9](https://github.com/mruby/mruby/commit/96641c9))
# Fixed GitHub Issues
+1
View File
@@ -74,6 +74,7 @@ OPCODE(KARG, BB) /* R[a] = kdict[Syms[b]]; kdict.delete(Syms[b]) */
OPCODE(RETURN, B) /* return R[a] (normal) */
OPCODE(RETURN_BLK, B) /* return R[a] (in-block return) */
OPCODE(RETSELF, Z) /* return self */
OPCODE(RETNIL, Z) /* return nil */
OPCODE(BREAK, B) /* break R[a] */
OPCODE(BLKPUSH, BS) /* R[a] = block (16=m5:r1:m5:d1:lv4) */
OPCODE(ADD, B) /* R[a] = R[a]+R[a+1] */
+6 -1
View File
@@ -1151,7 +1151,12 @@ gen_return(codegen_scope *s, uint8_t op, uint16_t src)
rewind_pc(s);
genop_0(s, OP_RETSELF);
}
else if (data.insn != OP_RETURN && data.insn != OP_RETSELF) {
else if (data.insn == OP_LOADNIL && src == data.a && op == OP_RETURN) {
/* LOADNIL + RETURN -> RETNIL */
rewind_pc(s);
genop_0(s, OP_RETNIL);
}
else if (data.insn != OP_RETURN && data.insn != OP_RETSELF && data.insn != OP_RETNIL) {
genop_1(s, op, src);
}
}
+3
View File
@@ -399,6 +399,9 @@ codedump(mrb_state *mrb, const mrb_irep *irep, FILE *out)
CASE(OP_RETSELF, Z):
fprintf(out, "RETSELF\n");
break;
CASE(OP_RETNIL, Z):
fprintf(out, "RETNIL\n");
break;
CASE(OP_BREAK, B):
fprintf(out, "BREAK\t\tR%d\t", a);
print_lv_a(mrb, irep, a, out);
+8
View File
@@ -2744,6 +2744,10 @@ RETRY_TRY_BLOCK:
a = 0;
goto NORMAL_RETURN;
}
CASE(OP_RETNIL, Z) {
a = 0;
goto L_RETURN_NIL;
}
CASE(OP_RETURN, B) {
mrb_int acc;
mrb_value v;
@@ -2751,6 +2755,10 @@ RETRY_TRY_BLOCK:
NORMAL_RETURN:
v = regs[a];
goto L_RETURN;
L_RETURN_NIL:
v = mrb_nil_value();
L_RETURN:
mrb_gc_protect(mrb, v);
return_ci = ci;
CHECKPOINT_RESTORE(RBREAK_TAG_BREAK) {