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)
```
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>
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>
- Add mrb_likely/mrb_unlikely macros to common.h for branch prediction
- Optimize OP_GETIDX array fast path:
- Cache RArray pointer to avoid repeated RARRAY() calls
- Single ARY_EMBED_P check instead of two (via RARRAY_LEN + RARRAY_PTR)
- Use unsigned comparison for bounds check
- Add branch prediction hints for common cases
- Convert switch statement to if-else chain for better branch prediction
Benchmark shows ~3% improvement for array read operations.
Co-authored-by: Claude <noreply@anthropic.com>
1. Change CASE(OP_DEBUG, Z) to CASE(OP_DEBUG, BBB) to match the
definition in include/mruby/ops.h. The previous code declared Z
(no operands) but then manually called FETCH_BBB(), which caused
incorrect behavior with extended opcodes (OP_EXT1/2/3).
2. Add NULL check before calling debug_op_hook, consistent with
how code_fetch_hook is handled. This prevents crashes when
MRB_USE_DEBUG_HOOK is enabled but no hook function is set.
Fixes: #5686
expose the previously internal outer_class() function as a public API
for mrbgems to retrieve the enclosing class/module of a given class.
closes#6705.
Co-authored-by: Claude <noreply@anthropic.com>
when the allocator can extend the block in place, realloc avoids
the overhead of malloc+memcpy+free. the keys are moved to their
new position with memmove and extended regions are cleared.
Co-authored-by: Claude <noreply@anthropic.com>
saves 40% memory (60 -> 36 bytes) for objects with 1-2 instance
variables, which is common for simple value objects like Point(@x, @y).
the trade-off is one extra reallocation when growing from 2 to 4 IVs,
but this is negligible since reallocations are rare compared to lookups.
Co-authored-by: Claude <noreply@anthropic.com>
when comparing bigint values with <=> operator, the comparison would
convert both operands to float, losing precision for values > 2^53.
this caused incorrect results like (10^20+1) <=> (10^20+2) returning 0
instead of -1.
add direct bigint comparison paths in cmpnum() to avoid float conversion
when both operands can be handled by mrb_bint_cmp().
Co-authored-by: Claude <noreply@anthropic.com>
With this PR, I can remove the original task.c in picoruby/picoruby and future development will be much easier.
## Add
### General
- C API functions exported with MRB_API for external integration:
- mrb_execute_proc_synchronously() for synchronous proc execution
- Task control APIs (mrb_create_task, mrb_suspend_task, mrb_resume_task, mrb_terminate_task, mrb_stop_task, mrb_task_value, mrb_task_status)
- Task context management APIs for picoruby-sandbox (mrb_task_init_context, mrb_task_reset_context, mrb_task_proc_set)
- Task.tick class method to get current tick count
- Comprehensive C API documentation with WASM integration examples
### For PicoRuby.wasm
- WASM/Emscripten support: Disable SIGALRM timer when __EMSCRIPTEN__ is defined, as JavaScript handles tick calls via setInterval
- Scheduler lock mechanism to prevent asynchronous task operations during synchronous execution (scheduler_lock counter in mrb_task_state)
- mrb_task_run_once() for single-step execution (event loop integration)
## Fix
### task.c
- Replace MRB_FIBER_TERMINATED with MRB_TASK_STOPPED just for clarity
- Allow suspending DORMANT and WAITING tasks in mrb_task_suspend (See comment in the source)
- Task context initialization by removing dummy callinfo push/pop
*NOTE*
With the dummy callinfo code that I deleted, IRB in PicoRuby ended SEGV.
If that code is mandatory, we need to discuss how to solve my problem.
### vm.c
- Handle MRB_TASK_CREATED status in VM's NORMAL_RETURN phase to properly stop tasks
----
These changes are necessary to make PicoRuby work.
Nevertheless, even with this patch, MicroRuby for Raspberry Pi Pico 2 is still unstable.
I would like to merge this PR anyway to make development easier by involving the PicoRuby community.
rename mrb_alloca() to mrb_temp_alloc() for clearer naming - the new name
better describes its purpose as GC-managed temporary allocation. keep
mrb_alloca() as a macro alias for backward compatibility.
apply mrb_temp_alloc() to fix potential memory leaks in:
- mruby-strftime: if mrb_str_cat() raises, allocated buffers now cleaned by GC
- mruby-io File.readlink: if mrb_str_new() raises, buffer now cleaned by GC
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_assert with mrb_ensure_*_type for VM opcodes that require
specific types:
- OP_ARYCAT: mrb_ensure_array_type
- OP_ARYPUSH: mrb_ensure_array_type
- OP_ASET: mrb_ensure_array_type (also fixed: was checking wrong register)
- OP_INTERN: mrb_ensure_string_type
- OP_HASHCAT: mrb_ensure_hash_type
These checks catch codegen bugs with clear error messages in both
debug and release builds.
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_assert with mrb_ensure_string_type to catch codegen bugs
even in release builds. This prevents null-dereference crashes when
OP_STRCAT receives a non-string first operand due to compiler bugs.
Consistent with OP_HASH which uses mrb_ensure_hash_type for similar
type safety.
Co-authored-by: Claude <noreply@anthropic.com>
The key variable in insertion_sort temporarily holds an array element
that's been removed from its slot during the sorting process. When
sort_cmp yields to a block that triggers GC, key wasn't protected
and could be collected.
Use arena save/restore around the loop to avoid arena overflow for
large arrays.
Test case from oss-fuzz: sort! with block containing rescue.
Co-authored-by: Claude <noreply@anthropic.com>
Add #undef lesser after last usage to prevent macro redefinition
warnings when files are amalgamated into a single translation unit.
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_obj_class() with direct mrb_obj_ptr(va)->c access:
- Skips unnecessary mrb_immediate_p() check (these types are never immediate)
- Skips mrb_class_real() traversal for singleton classes
- Objects with singleton methods now fall back to method dispatch
(correct behavior since they might have overridden []/[]=)
Co-authored-by: Claude <noreply@anthropic.com>
Add inline optimizations for Array#[]= and Hash#[]= in OP_SETIDX,
matching the pattern established for OP_GETIDX:
- Array class: use mrb_ary_set() directly (integer index only)
- Hash class: use mrb_hash_set() directly
- Subclasses: fall back to method dispatch (can override []=)
- String: unchanged (complex 2-3 argument signature)
Co-authored-by: Claude <noreply@anthropic.com>
Apply the same pattern as the Array/Hash fix: the OP_GETIDX optimization
now only applies to instances of the String class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.
Co-authored-by: Claude <noreply@anthropic.com>
Apply the same pattern as the Hash fix: the OP_GETIDX optimization
now only applies to instances of the Array class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.
Co-authored-by: Claude <noreply@anthropic.com>
The OP_GETIDX optimization now only applies to instances of the Hash
class itself. Subclasses fall back to method dispatch, allowing them
to override the [] method. This fixes compatibility with libraries
like mruby-hashie that rely on aliasing/overriding [] in subclasses.
Trade-off: Hash#[] cannot be overridden on the Hash class itself
(only on subclasses). This is a reasonable semantic for mruby since
subclassing is the proper pattern for customization.
Co-authored-by: Claude <noreply@anthropic.com>
Move casecmp_p from mruby-string-ext and mruby-encoding to core as
mrb_strcasecmp_p (predicate function returning mrb_bool). Add
MRB_STR_CASECMP_P macro to internal.h for comparing mrb_value strings
with literal strings.
This eliminates code duplication and avoids static function name
collision for future amalgamation support.
Co-authored-by: Claude <noreply@anthropic.com>
Extract duplicated UTF-8 codepoint-to-bytes encoding into a shared
function in src/string.c. Update all gems to use it:
- mruby-sprintf: %c specifier
- mruby-io: putc
- mruby-string-ext: Integer#chr
- mruby-pack: pack("U")
- mruby-compiler: Unicode escapes in parser
Also use existing mrb_utf8len() in io.c for character length detection.
Co-authored-by: Claude <noreply@anthropic.com>
add support for one-line pattern matching syntax:
- 'expr in pattern' returns true/false
- 'expr => pattern' raises NoMatchingPatternError on mismatch
add NODE_MATCH_PAT node type for both forms, distinguished by
raise_on_fail flag. grammar rules placed at expr level to avoid
conflict with rescue clause's exception variable syntax.
Co-authored-by: Claude <noreply@anthropic.com>
reduce code duplication by introducing MRB_PROC_RESOLVE_ALIAS macro
to handle alias proc resolution in a consistent way across 5 locations.
Co-authored-by: Claude <noreply@anthropic.com>
follow alias chains in mrb_proc_eql() to compare underlying procs,
making Method#== return true for aliased methods as in CRuby.
also fix typo where p1 was checked instead of p2 in CFUNC comparison.
Co-authored-by: Claude <noreply@anthropic.com>
refactored the NULL pointer guard in mrb_str_cmp() from an if-else
block to a more concise ternary operator. functionality remains the
same: avoids undefined behavior by skipping memcmp() when comparing
zero-length strings.
Co-authored-by: Claude <noreply@anthropic.com>
passing NULL pointers to memcmp() is undefined behavior per C standard,
even when size is 0. memcmp() is declared with nonnull attributes,
and ASAN can detect this violation.
in mrb_str_cmp(), when comparing two empty strings or when the minimum
length is 0, we now skip the memcmp() call and directly set retval to 0.
this avoids the undefined behavior while maintaining correct comparison
semantics.
Co-authored-by: Claude <noreply@anthropic.com>
the keyword argument handling code was checking if kdict is not nil
before calling mrb_hash_size(), but didn't verify it's actually a hash.
malformed bytecode could cause a non-hash value to be stored in the
keyword dictionary register, leading to a NULL pointer dereference in
h_size(). add mrb_hash_p() check to prevent the crash.
Co-authored-by: Claude <noreply@anthropic.com>