The current implementation of `task_init_context` inheriting a receiver from the parent task is unstable and causes critical faults, especially on microcontrollers.
- It leads to a HardFault on devices like Raspberry Pi Pico 2 by accessing a potentially NULL `mrb->c->ci`.
- Even when `mrb->c->ci` is not NULL, this incomplete context copy causes other memory errors (SEGV).
This patch reverts to the safer, previous behavior, that I implemented in picoruby/picoruby, of always initializing a new task's receiver to `top_self`, ensuring predictable and
robust operation.
The issue was likely masked on POSIX systems due to the unpredictable nature of undefined behavior.
Old code:
```c
t = q_ready_;
/* No task ready - check if all tasks are done */
if (!t) {
/* If there are tasks waiting or suspended, idle */
if (q_waiting_ || q_suspended_) {
mrb_hal_task_idle_cpu(mrb);
continue;
```
IRQ possibly happens between `t = q_ready_;` and `if (q_waiting_ || q_suspended_) {` and, for example, a waiting task may move to the ready queue.
As a result, the infinite loop in mrb_task_run unexpectedly breaks in spite of not all the task is dormant.
This patch fixes the issue above by setting the `exitting` condition with a critical section.
the second NULL in the initializer was for the removed gcnext field,
causing "makes integer from pointer" warning on the tt bitfield.
Co-authored-by: Claude <noreply@anthropic.com>
remove per-object gcnext pointer from MRB_OBJECT_HEADER, saving one
word (8 bytes on 64-bit) per object slot. the gray list for tri-color
marking is replaced by a fixed-size stack (MRB_GRAY_STACK_SIZE=1024)
in mrb_gc. when the stack overflows, a linear heap rescan recovers
gray objects.
object slot size: 48 -> 40 bytes (16.7% reduction on 64-bit).
benchmarks show up to 12% RSS reduction on object-heavy workloads
with neutral performance impact.
Co-authored-by: Claude <noreply@anthropic.com>
OUTINT macro checked ayear > INT_MAX, but timegm() later computes
tm_year + TM_YEAR_BASE (1900), which overflows when tm_year is near
INT_MAX. Tighten the upper bound to INT_MAX - TM_YEAR_BASE.
Found by ClusterFuzz.
Co-authored-by: Claude <noreply@anthropic.com>
Same reasoning as the vm.c change - the keyword hash arriving at
C functions via mrb_get_args() is always freshly constructed at the
call site, so duplication is unnecessary.
Co-authored-by: Claude <noreply@anthropic.com>
The keyword argument hash passed to a method is always freshly
constructed at the call site - either by hash_new_from_regs() in
OP_SEND for inline keyword pairs, or by OP_HASH/OP_HASHCAT for
compiler-generated keyword arguments (including the **h splat case
which creates OP_HASH(0)+OP_HASHCAT). Since no caller retains a
reference to this hash, the mrb_hash_dup() was redundant.
Co-authored-by: Claude <noreply@anthropic.com>
Zero-initialize the mirb_editor struct to prevent highlight.enabled
from containing garbage values when stdin is not a tty (e.g. in
bintest). Without this, ANSI color codes could be emitted in
non-interactive mode, breaking output string matching in tests.
Co-authored-by: Claude <noreply@anthropic.com>
Same issue as the pool string fix: the bounds check for
symbol names only validated snl bytes, but the binary
format includes a null terminator. The source pointer
advances by snl+1, so the check must account for it.
Co-authored-by: Claude <noreply@anthropic.com>
The bounds check for IREP_TT_STR pool data only validated
pool_data_len bytes, but the binary format includes a null
terminator after the string content. Both memcpy and the
source pointer advance by pool_data_len+1, so the check
must account for the extra byte.
Co-authored-by: Claude <noreply@anthropic.com>
prek is a faster, Rust-based drop-in replacement for pre-commit.
It reads the same .pre-commit-config.yaml with no changes needed.
Co-authored-by: Claude <noreply@anthropic.com>
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>
Reject MRB_INT_MAX length strings to prevent signed integer overflow
when adding 1 for the null terminator in str_init_normal_capa() and
resize_capa().
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>
Replace per-symbol mrb_malloc() with a chunk-based string pool that
batches allocations into 4KB chunks. This reduces malloc call count
by ~12x (e.g. 909 vs 10,887 for 10k dynamic symbols) and eliminates
per-allocation malloc metadata overhead (~16 bytes/symbol).
Pool allocations are rounded up to even size to preserve LSB pointer
tagging used for literal detection.
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>
Change default stack growth from linear (+128) to exponential (1.5x).
This reduces reallocation frequency while maintaining reasonable memory
usage. The minimum growth is still MRB_STACK_GROWTH (128) to ensure
small programs don't over-allocate.
MRB_STACK_EXTEND_DOUBLING (2x growth) remains available for maximum
performance when memory is not a concern.
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>
Both opcodes had nearly identical code for creating procs and
defining methods. Now they share a common L_DEF_METHOD label,
reducing code duplication by ~10 lines.
Co-authored-by: Claude <noreply@anthropic.com>
Replace block.call(x) with yield x in core iteration methods to take
advantage of the new OP_BLKCALL optimization. This improves Integer#times
by 13% and Array#each by 7%.
Methods updated:
- Integer#times, Integer#upto, Integer#downto
- Array#each, Array#each_index
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>