The MATCHERR optimization replaced JMPNOT (BS, 4 bytes) with
MATCHERR (B, 2 bytes) and rewound s->pc by 2. When pattern
alternation (e.g. a|B) dispatched a success jump to s->pc before
the optimization, the rewind shifted subsequent instructions and
the jump landed in the middle of the next instruction, causing
out-of-bounds access at runtime.
Replace JMPNOT in-place with MATCHERR+NOP+NOP to keep the same
4-byte size, so s->pc does not change and jump targets stay valid.
Co-authored-by: Claude <noreply@anthropic.com>
CRuby raises SyntaxError for `^a` in pattern matching when `a` is
not a local variable. Previously mruby silently generated an
unconditional fail jump, which also led to bytecode corruption
when combined with alternation patterns.
Co-authored-by: Claude <noreply@anthropic.com>
The JMPNOT-to-JMPIF optimization in NODE_PAT_ALT assumed the fail
chain always ends with OP_JMPNOT (format BS), but NODE_PAT_PIN
generates OP_JMP (format S) when the pinned variable is undefined.
Writing OP_JMPIF at left_fail-2 then corrupts the preceding
instruction's operand, causing out-of-bounds pool access at runtime.
Co-authored-by: Claude <noreply@anthropic.com>
Barrett and Montgomery reduction compute 2^(2k) internally where k
is the modulus bit length. When this exceeds MRB_BIGINT_BIT_LIMIT,
mrb_raise() via longjmp skips cleanup of allocated temporaries.
Add early modulus size check before any heap allocation.
Co-authored-by: Claude <noreply@anthropic.com>
The work buffer size in mpz_montgomery_reduce() was calculated as
x_len + k + 2, which assumed x_len >= k. However, when R^2 mod n
produces a small result, x_len can be much smaller than k.
The Montgomery reduction loop writes k limbs at work[i] for each
iteration i=0..k-1, so the maximum index accessed is work[2k-1].
This requires at least 2k limbs in the work buffer.
Fixed by ensuring work_size is at least 2*k+2 limbs when x_len < k.
Also initialize b->as.heap before mpz_move in bint_set() to ensure
the destination mpz_t has valid initial state.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_mul_sparse allocated temporary mpz_t variables (shifted, temp) that
were leaked when an exception was raised (e.g., RangeError from shift
width too large). bint_mul had the same issue with its output mpz_t z.
Wrap both functions with MRB_ENSURE to guarantee cleanup runs regardless
of exceptions, following the existing pattern used by mpz_mul_all_ones.
Co-authored-by: Claude <noreply@anthropic.com>
Use syntax highlighter for result values instead of single color.
Add support for hash key symbol syntax (e.g., `a:` in `{a: 1}`).
Co-authored-by: Claude <noreply@anthropic.com>
Previously, the identity of the proc object was verified solely based on the identity of irep.
This patch makes the behavior consistent with CRuby.
The reason I noticed this issue was that when adding multiple proc objects with the same irep to a set object, only one was added.
```ruby
p Set.new(Array.new(3) { -> {} }).size
# => 3 (Ruby 4.0)
# => 1 (mruby without this patch)
```
If the block scope is the same, there is only one in CRuby as well.
However, in CRuby, the result of `Proc#to_s` is not affected by the block scope, so it has been changed to be based on the object's address.
The reason no test for `Proc#to_s` was added is that I couldn't determine whether it should be based on `Proc#hash` or the object's address.
```ruby
b = []
t = 3
while t > 0
b << -> {}
t -= 1
end
p Set.new(b).size
# => 1 (Ruby 4.0 and mruby)
p b[0].to_s == b[1].to_s
# => false (Ruby 4.0)
# => true (mruby without this patch)
```
The first keyword argument was dropped because gen_hash() was
called with callargs->keyword_args->cdr instead of
callargs->keyword_args.
Co-authored-by: Claude <noreply@anthropic.com>
Preparation for future grammar simplification that may
require parentheses for method calls with arguments on
the right-hand side of assignments.
Co-authored-by: Claude <noreply@anthropic.com>
These opcodes use BB format instead of BBB, saving 1 byte per call.
In the standard library, this saves ~790 bytes (568 SEND0 + 222 SSEND0).
Co-authored-by: Claude <noreply@anthropic.com>
Add single-byte opcodes for returning true/false directly, completing
the set of literal return opcodes (RETSELF, RETNIL, RETTRUE, RETFALSE).
Codegen applies peephole optimization to fuse LOADTRUE/LOADFALSE + RETURN.
Co-authored-by: Claude <noreply@anthropic.com>
Rename boolean load opcodes for consistency with LOADNIL/LOADSELF.
Backward compatibility aliases are provided in opcode.h.
Co-authored-by: Claude <noreply@anthropic.com>
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>
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>
TDEF fuses TCLASS+METHOD+DEF for normal method definitions.
SDEF fuses SCLASS+METHOD+DEF for singleton method definitions.
Saves 4 bytes per method definition (8 bytes -> 4 bytes).
Falls back to unfused instructions if irep index exceeds 255.
Co-authored-by: Claude <noreply@anthropic.com>
Fuses MOVE+LOADI_0+GETIDX pattern into single instruction.
Saves 4 bytes per arr[0] access (7 bytes -> 3 bytes).
Co-authored-by: Claude <noreply@anthropic.com>
fuse MOVE+ADDI+MOVE and MOVE+SUBI+MOVE patterns into single instructions.
ADDILV/SUBILV add/subtract an immediate to a local variable in-place.
BBB format: a=local, b=working space for method call, c=immediate.
saves 5 bytes per instance (9->4 bytes), 40 occurrences in stdlib.
Co-authored-by: Claude <noreply@anthropic.com>
Change OP_MATCHERR from Z format (unconditional) to B format
(conditional on register). This allows fusing JMPIF + MATCHERR
sequence into a single MATCHERR instruction for simple patterns.
Before: JMPIF R2 target (4 bytes) + MATCHERR (1 byte) = 5 bytes
After: MATCHERR R2 (2 bytes)
Saves 3 bytes per pattern match with raise_on_fail.
Co-authored-by: Claude <noreply@anthropic.com>
Replace 4-instruction sequence (GETCONST + STRING + SEND + RAISEIF)
with single OP_MATCHERR instruction that raises NoMatchingPatternError
with "pattern not matched" message.
Bump RITE binary format version from 0300 to 0400 due to opcode
number shift.
Co-authored-by: Claude <noreply@anthropic.com>
Added error handling for file descriptors larger than FD_SETSIZE in mrb_hal_io_fdset_set and mrb_hal_io_fdset_isset functions, for posix hal.
I actually don't know how to fix this on windows, or if it needs fixing.
The purpose is to avoid using the `MRB_TT_CPTR` object.
The reasons are as follows:
- The `MRB_WORD_BOXING` setting involves object creation.
- If object creation fails, the `ary_set_t` data leaks memory.
wrap hash and eql callbacks with mrb_protect_error() to catch exceptions
during khash table rebuild. when an exception occurs (e.g., SystemStackError
from infinite recursion), return a safe default value and store the exception
in mrb->exc for later processing. this prevents memory leaks from orphaned
allocations when exceptions propagate through khash rebuild.
Co-authored-by: Claude <noreply@anthropic.com>
Function was only called once and contained just 2 lines of code.
Inlining directly reduces code size and improves clarity.
Co-authored-by: Claude <noreply@anthropic.com>
Add CPython-style parsing for base-10 string to integer conversion:
- Parse 9 digits at a time into decimal-base array
- Convert decimal-base to binary in single pass
- Use memory pool for temporary decimal buffer
- Use realloc for result buffer to reduce allocations
Also add digit_pairs lookup table for faster to_s output.
Performance: 2-5x faster for to_i, 60% fewer allocations.
Co-authored-by: Claude <noreply@anthropic.com>
- Add mrb_gc_protect() after arena_restore to prevent result from being collected before returning to caller
- Add comment to suspend_task_internal explaining why WAITING and DORMANT tasks can also be suspended
- Move argc/argv cast at the beginning of function with comment
This improves to_s performance for medium-sized bigints (40-50 limbs,
~800-1000 digits) by approximately 5x by enabling the divide-and-conquer
algorithm earlier.
Benchmark results:
40 limbs (772 digits): 88 us -> 18 us (5x faster)
50 limbs (964 digits): 134 us -> 25 us (5.4x faster)
Co-authored-by: Claude <noreply@anthropic.com>
Benchmarks show the previous threshold of 50 was too low, causing
Toom-3's setup overhead to outweigh its asymptotic benefits for
medium-sized numbers. Raising to 100 limbs provides:
- 2x faster at 300 limbs (192 -> 96 us)
- 2.5x faster at 120 limbs (42 -> 17 us)
- 2.6x faster at 80 limbs (31 -> 12 us)
Co-authored-by: Claude <noreply@anthropic.com>
When multiplying numbers where one is significantly larger than the other
(at least 2x size difference), split the larger number into chunks matching
the smaller number's size, multiply each chunk, and combine results. This
avoids pathological performance when Toom-3 pads asymmetric operands with
zeros.
Benchmarks show 6-16x speedup for size ratios from 10:1 to 40:1, with no
regression for symmetric cases.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_init_heap() already allocates the requested size, so immediately
calling mpz_realloc() with the same size is a no-op. Remove these
redundant calls from mpz_and, mpz_or, mpz_xor, mpz_mod_2exp, and
mpz_abs.
Co-authored-by: Claude <noreply@anthropic.com>