Commit Graph

6430 Commits

Author SHA1 Message Date
Yukihiro "Matz" Matsumoto c8c8522519 Merge pull request #6218 from dearblue/init-functions
Reduced description of `mrb_init_core()`
2026-05-08 19:21:47 +09:00
Yukihiro "Matz" Matsumoto 61a5e290b3 Merge pull request #6334 from dearblue/each_objects
Remove `iterating` variable from `mrb_objspace_each_objects()`
2026-05-08 12:01:49 +09:00
Yukihiro "Matz" Matsumoto e4662551a3 Merge pull request #6577 from dearblue/ary_dup
Sharing arrays with `ary_dup()`
2026-05-08 11:15:00 +09:00
Yukihiro "Matz" Matsumoto 90a0fc5797 Merge pull request #6576 from dearblue/ary_replace
Share array entities if possible with `ary.replace(frozen_ary)`
2026-05-08 11:09:50 +09:00
Pete Kinnecom ebfdf8ae43 Fix Class inherited hook ordering
In CRuby the inherited method is invoked before Class.new yields to the
block.

An "already initialized error" exception is also removed since the
inherited method is now invoked before initialization and can set
instance variables on a class.
2026-05-07 15:51:30 +00:00
Yukihiro "Matz" Matsumoto c6836f494a fp_uscale.c: fix uninitialized *fp when exponent is malformed
mrb_read_float jumped past the *fp assignment via `goto done` when
the exponent had no digits (e.g., "5e", "5e+"). It returned TRUE
without setting *fp, leaving the caller (mrb_str_to_dbl etc.) to
return whatever was on the stack. MSan flagged this; on most runs
the uninitialized read happens to yield 0.0, so the bug is silently
incorrect rather than crashing.

Refactor the finalization (compute res from d, final_p, sign, etc.)
to run once after the optional-exponent block. The malformed-exponent
case now falls through using the mantissa-only `final_p = trunc - dp`,
producing the same result strtod gives for the same input ("5e" -> 5.0
with endp at 'e'). Float("5e") still raises because mrb_str_len_to_dbl
rejects trailing characters under badcheck.

Reported by OSS-Fuzz (MSan).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-06 09:34:41 +09:00
Yukihiro "Matz" Matsumoto ddcbd2dc90 fp_uscale.c: fix shift and clz UB in tiny-float formatting
Two UBSan issues exposed by sprintf("%f", 1e-7) and similar:

1. uscale() shifted hi by c.s without bounding c.s, hitting UB
   when c.s >= 64. The mask line had `c.s & 63`, but the actual
   `hi >> c.s` line did not, so the partial guard was incomplete.
   On x86 the hardware silently masks the shift, producing wrong
   output ("1844674407370.955078" for 1e-7) instead of crashing.
   When c.s >= 64 the value rounds to 0 with sticky=1, so we can
   bail early.

2. count_digits(0) called bits_len64(0) -> clz64(0), which is UB.
   The only other bits_len64 caller already guards d == 0; align
   count_digits with that pattern. Returning 1 (since "0" is one
   digit) preserves output formatting.

Reported by OSS-Fuzz (clusterfuzz testcase 5210395240628224).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-04 07:53:00 +09:00
Yukihiro "Matz" Matsumoto 8a73faf61e vm.c: refresh ci after mrb_const_set in OP_SETCONST
mrb_const_set may invoke const_added via mrb_funcall_argv, which
re-enters the VM and can reallocate cibase. This invalidates the
local ci pointer (and thus the regs macro that expands to ci->stack),
causing a use-after-free on the next opcode dispatch.

OP_SETMCNST and the OP_GET* / OP_*IDX opcodes already follow this
pattern; align OP_SETCONST with them.

Reported by OSS-Fuzz (clusterfuzz testcase 5886006653157376).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-02 10:42:41 +09:00
Yukihiro "Matz" Matsumoto 1702b89c25 Merge pull request #6787 from dearblue/sprintf 2026-04-28 12:56:07 +09:00
Yukihiro "Matz" Matsumoto bbf7c43c37 array.c: restore arena around heap sort per-call protections
heapify() and heap_delete_root() call mrb_gc_protect() to guard a
C-local mrb_value across potential GC points inside sort_cmp().
They were missing the matching mrb_gc_arena_save/restore pair, so
each call leaked one arena slot.  For O(n log n) heapify calls under
MRB_GC_FIXED_ARENA this overflows the 100-slot arena and raises
NoMemoryError, e.g. during Array#repeated_permutation tests.

Wrap each function body with mrb_gc_arena_save/restore, matching
the pattern already used in insertion_sort() above.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:39 +09:00
Yukihiro "Matz" Matsumoto eb5480a6b3 hash.c: fix float_hash_code() for zero values
Remove early return of hardcoded 0 for zero floats. Instead, normalize
-0.0 to +0.0 and pass through mrb_byte_hash() for better distribution.
The previous second condition (f == -0.0) was dead code since IEEE 754
-0.0 == 0.0 is true.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:39 +09:00
Yukihiro "Matz" Matsumoto b3777110ab hash.c, string.c: improve hash function quality
Use FNV-1a (xor-then-multiply) instead of FNV-1 for better avalanche
in byte hashing. Strengthen the hash finalizer in mrb_obj_hash_code()
with multiply-xorshift to improve distribution for integer and symbol
keys with power-of-two table sizes.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto 7ae73ac7e4 class.c: extract mrb_get_args fast path validator to inline function
Replace the inline switch with a separate fast_fmt_ok() function that
maps each format character to a validity code (0=invalid, 1=arg spec,
2=separator). Modern compilers generally lower this to a jump table,
so the per-character cost remains effectively O(1).

Keeping this as a switch (instead of a C99 array-index designator
lookup table) also lets the file compile cleanly as C++.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 966aa9512e class.c: add fast path in mrb_get_args for simple format strings
Skip the two-pass format scanning when the format string contains
only simple specifiers (o, S, i, n, z, b, f, A, H, c, s, a) with
optional '|' separator. Validates all specifiers before consuming
va_list to ensure safe fallback to the slow path.

Covers ~50% of all mrb_get_args call sites in the codebase (175
of 353). Reduces per-call argument parsing overhead by ~30-40%.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 3d8ccee7a8 vm.c: optimize cipush/cipop for common cases
CI_PROC_SET: split NULL/non-NULL proc paths so the compiler can
eliminate the CFUNC/ALIAS checks when proc is a compile-time NULL
(8 of 11 cipush call sites).

cipop: add fast path for the common case where no env and no blk
are set. skips ci_env_set, orphan check, and env_unshare entirely.
most simple method calls (no blocks, no closures) take this path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +09:00
Yukihiro "Matz" Matsumoto 4307461e58 gc.c: add symbol_count and dynamic_symbol_count to GC.stat
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 49dff412b5 gc.c: extract mrb_obj_alloc_core() for internal allocation
split mrb_obj_alloc() into type-validation wrapper and allocation
core (mrb_obj_alloc_core). internal callers (mrb_proc_new,
mrb_env_new) use the core directly, skipping 15+ lines of type
validation per allocation.

most impactful for workloads with heavy Proc/Env allocation
(lambda calculus, block-intensive code).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +09:00
Yukihiro "Matz" Matsumoto f7d7bbef43 array.c: add string-specialized fast path for Array#sort!
when all elements are plain String (not subclass) and no block is
given, use specialized sort that calls mrb_str_cmp() directly,
bypassing sort_cmp overhead (GC arena, type dispatch, array
modification check).

includes subclass check to ensure String#<=> is not overridden.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +09:00
Yukihiro "Matz" Matsumoto 5364c4167e array.c: add integer-specialized fast path for Array#sort!
when all elements are integers and no block is given, use
specialized heapify/insertion_sort that compare mrb_int values
directly, bypassing sort_cmp entirely. this eliminates per-comparison
overhead of GC arena save/restore, type checking, and array
modification checks.

the pre-scan to detect all-integer arrays is O(n), negligible
compared to O(n log n) sort. non-integer and block sorts are
unaffected.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto 02bb943960 array.c: optimize heap sort with hole-style sift-down and Floyd's method
two improvements to Array#sort!'s heap sort:

1. hole-style sift-down: save root value, move larger children up
   one at a time, write saved value once at the end. reduces
   assignments from 3 per level (swap) to 1 per level (move).

2. Floyd's bottom-up heap deletion: during extraction phase, sift
   the hole down to a leaf using only child-child comparisons
   (~1 comparison per level), then sift up to find the correct
   position. this reduces average comparisons from ~2 log n to
   ~log n per extraction, nearly halving the total comparison
   count for the sort.

both changes preserve O(n log n) worst case and O(1) extra space.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto e292d7a6c4 symbol.c: implement lazy symbol GC (mark-sweep)
when dynamic symbol count reaches MRB_SYMBOL_MAX, run a mark-sweep
pass over all live objects to identify referenced symbols. sweep
unreferenced dynamic symbols, freeing their individually-allocated
string data and marking symtbl slots as tombstones.

mark phase traverses:
- all heap objects (method tables, IV tables, arrays, hashes, envs)
- VM stack values (MRB_TT_SYMBOL)
- call stack method IDs (ci->mid)
- root and current context

after sweep, rebuild hash table to maintain valid collision chains.

this completes the A+ symbol GC plan: the limit acts as a GC
trigger rather than a hard cap. unreferenced DoS symbols are
reclaimed, allowing legitimate code to continue.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto cb64a0b4a4 symbol.c: use individual malloc for dynamic symbol strings
dynamic symbols (created via to_sym, send, etc.) now use
mrb_malloc() instead of sym_pool_alloc(). this makes them
individually freeable by future symbol GC.

static symbols (presym, mrb_intern_static, literals) continue
to use the pool allocator for compact storage.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto afc0753c1d symbol.c: add dynamic symbol limit (MRB_SYMBOL_MAX)
track dynamic (runtime-created) symbols separately from presyms,
inline symbols, and static C API symbols. raise RuntimeError when
the dynamic symbol count exceeds MRB_SYMBOL_MAX (default 4096).

this prevents DoS attacks via unbounded symbol creation (e.g.
"str".to_sym in a loop). presyms and inline symbols are not
counted toward the limit.

infrastructure for future symbol GC: sym_flags array tracks
per-symbol metadata (SYM_FL_DYNAMIC flag).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto 0b79c70935 dump.c: consolidate error handling in mrb_dump_irep_cfunc()
replace four repeated `mrb_free(mrb, bin); return MRB_DUMP_WRITE_FAULT`
sequences with a single goto-based cleanup path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:25 +09:00
Yukihiro "Matz" Matsumoto 0f47249963 class.c: clear const cache on include/prepend
mrb_include_module() and mrb_prepend_module() did not invalidate
the constant cache. stale cache entries caused incorrect constant
resolution after include changed the ancestor chain.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:25 +09:00
Yukihiro "Matz" Matsumoto 3b85d48f89 class.c: fix module/class reopening via include
mrb_vm_define_module() and mrb_vm_define_class() incorrectly
reopened modules/classes accessible through include rather than
creating new ones. CRuby only reopens modules directly defined
on the outer scope.

the internal define_module()/define_class() use
mrb_const_defined_at() which walks ancestors for Object class.
bypass them and create modules/classes directly in the VM path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:25 +09:00
Yukihiro "Matz" Matsumoto d2caa144be cdump.c: consolidate sym_name_with_*_p into sym_name_with_suffix_p
three functions differed only in the trailing character check ('=',
'?', '!'). replace with a single parameterized function.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto f1a6274c34 vm.c: extract vm_call_proc() to consolidate OP_CALL and OP_BLKCALL
both opcodes share identical proc dispatch logic (alias resolution,
callinfo setup, cfunc/irep branching). the only difference is how
nargs is computed (ci_bidx vs operand b).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto 6a6e2b48ac vm.c: replace mrb_funcall_argv() with goto L_SEND_SYM in OP_MATHILV
avoid re-entrant VM call from C; use the same dispatch pattern as
OP_MATH and OP_MATHI for consistency.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto 82bb954c23 vm.c: extract vm_define_method() to consolidate OP_TDEF and OP_SDEF
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto 309f450bab gc.c: use actual work done for debt repayment in incremental step
Decrement gc_debt by the actual number of objects processed
instead of the fixed GC_STEP_SIZE. This makes step_ratio
directly affect debt repayment: larger steps repay more debt,
naturally reducing GC invocation frequency.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto 851da984b4 gc.c: use :debt instead of :threshold in GC.stat
Expose gc_debt directly as :debt in GC.stat without sign negation.
The debt model has no threshold ceiling, so :threshold was a
misleading name. Negative debt means credit, positive means GC
is behind on collection work.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:24 +09:00
Yukihiro "Matz" Matsumoto 878ecd9b09 gc.c: expose negated gc_debt as :threshold in GC.stat
Use -gc_debt as the :threshold key in GC.stat for familiarity.
Positive means credit remaining, negative means GC is overdue.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:23 +09:00
Yukihiro "Matz" Matsumoto f0b3fdfb14 gc.c: replace threshold model with debt-based GC trigger
Replace the threshold-based GC trigger (gc->threshold vs gc->live)
with a debt model (gc->gc_debt). Each allocation increments debt;
each GC step decrements by GC_STEP_SIZE. When a cycle completes,
credit is proportional to live_after_mark * interval_ratio, giving
a natural feedback loop that adapts to allocation rate.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:23 +09:00
Yukihiro "Matz" Matsumoto 4b866a84da gc.c: add step_limit and malloc_threshold for GC tuning
GC.step_limit caps the per-step work in incremental GC,
enabling more predictable pause times for real-time use.
GC.malloc_threshold triggers GC based on allocation bytes,
addressing memory pressure from large buffers.
Both default to 0 (disabled), preserving existing behavior.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:23 +09:00
Yukihiro "Matz" Matsumoto cfa422b872 gc.c: mark leaf objects directly without gray stack
Leaf types (String, Integer, BigInt, Complex, CPTR) have no children
besides their class pointer. Mark them black immediately in
mrb_gc_mark() instead of pushing to the gray stack, reducing gray
stack pressure and overflow frequency.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +09:00
Yukihiro "Matz" Matsumoto 60cde305c1 gc.c: implement GC.stat method
Return a Hash with GC statistics: live, threshold, state,
generational, full. When MRB_GC_STATS is defined, also includes
total, minor, major counters.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +09:00
Yukihiro "Matz" Matsumoto 52fb294172 gc.c: add optional GC statistics counters
Add gc_total_count, minor_gc_count, major_gc_count (uint32_t) to
mrb_gc, guarded by MRB_GC_STATS. Zero cost when disabled.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +09:00
Yukihiro "Matz" Matsumoto 78658d67e1 vm.c: extract OP_GETIDX, OP_GETIDX0, OP_SETIDX, OP_DIV into static helpers
Phase 2 of opcode handler extraction. these opcodes use
L_SEND_SYM/L_SENDB_SYM fallback for generic method dispatch when
the fast path (Array/Hash/String/Integer/Float) does not apply.
add VM_SEND_SYM and VM_SENDB_SYM return codes. move TYPES2 macro
to file scope for use by vm_op_div.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +09:00
Yukihiro "Matz" Matsumoto 95bfa86160 vm.c: extract OP_ENTER, OP_ARGARY, OP_BLKPUSH into static helpers
extract the three largest self-contained opcode handlers from the
mrb_vm_exec() dispatch loop into static functions. add
__attribute__((flatten)) to mrb_vm_exec() so that the compiler
inlines them back, producing identical binary output while keeping
the source clean.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +09:00
Yukihiro "Matz" Matsumoto 3d53864991 fp_uscale.c: add shortest representation for Float#to_s
Use uscale-based shortest() to compute the minimal decimal string
that uniquely identifies each double. This guarantees perfect
round-trip (parse(to_s(x)) == x) while keeping output concise
(e.g. 0.1 prints as "0.1", not "0.10000000000000001").

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +09:00
Yukihiro "Matz" Matsumoto 9ff1aa9d55 fp_uscale.c: replace fmt_fp.c and readfloat.c with uscale algorithm
Replace separate float formatting (fmt_fp.c) and parsing (readfloat.c)
implementations with a unified fp_uscale.c using 128-bit unrounded
scaling. Both mrb_format_float() and mrb_read_float() now share a
single pow10 table and uscale() primitive for decimal/binary conversion.

This fixes subnormal parsing accuracy (old code returned 0.0 for the
smallest subnormals) and corrects %.2f rounding for values like
12345.125. Table size grows from ~5KB to ~11KB in .rodata.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +09:00
Yukihiro "Matz" Matsumoto 50bc8c6136 vm.c: replace constant cache generation counter with direct invalidation
Remove the per-entry generation field and per-state generation
counter. Invalidation now clears entries directly, removing one
comparison from every OP_GETCONST hot path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +09:00
Yukihiro "Matz" Matsumoto d0d2c3072c class.c: use 2-way set-associative method cache
Right-shift class pointer by 4 before hashing to remove
always-zero alignment bits, improving hash distribution.
Organize 256 cache entries as 128 sets x 2 ways to reduce
conflict misses when multiple methods share a hash bucket.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +09:00
Yukihiro "Matz" Matsumoto 7675601b92 vm.c: add constant lookup cache with generation counter
Cache OP_GETCONST results in a global direct-mapped cache (64 entries)
keyed by (irep, sym). Invalidate all entries via a generation counter
bumped on mrb_const_set(), mrb_const_remove(), and
mrb_define_const_id(). ~10% faster on constant-heavy code; disabled
with MRB_NO_CONST_CACHE.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:20 +09:00
Yukihiro "Matz" Matsumoto 8996152365 Merge pull request #6786 from dearblue/sysfail 2026-04-23 19:15:38 +09:00
Yukihiro "Matz" Matsumoto 7365ed526b Merge pull request #6785 from khasinski/cfunc-proc-aspec 2026-04-23 19:11:35 +09:00
Yasuhiro Matsumoto b82f1c2ec0 readfloat.c: keep one extra fraction digit (17 -> 18)
18 decimal digits still fit in uint64_t (max ~1.8e19), so we can hold
one more digit of precision without overflow risk.
2026-04-16 17:13:17 +09:00
Yasuhiro Matsumoto 10c9e83128 readfloat.c: correctly round fraction via division by exact 10^n
The previous code computed `frac_part * pow10_negative[n]`, where
pow10_negative[n] is already a rounded approximation of 10^-n (since
10^-n is not exactly representable in binary). The multiplication then
adds another rounding step, leaving up to ~1 ulp of error.

Dividing `frac_part` by `pow10_positive[n]` is exact for n <= 22 (the
range where 10^n fits exactly in a double), so the division is the
only rounding and the result is correctly rounded. For example,
"0.3".to_f now matches the 0.3 literal's bit pattern (and libc strtod).
2026-04-16 14:47:14 +09:00
dearblue 6c06b4cd9d Early conversion of mesg to a string object in mrb_sys_fail()
`mrb_class_get_id()` may call the `#const_missing` method.
Therefore, if the `mesg` string originates from a string object, it may reference an invalid address.

And since `errno` might also change during the call to `#const_missing`, save this as well beforehand.

Also, while `mrb_class_defined_id()` does not currently call the `#const_defined?` method, it is unclear whether this will remain the case in the future.
2026-04-13 23:26:15 +09:00